Rs: A Strict Superset of Rust for Living Systems
Every .rs file is valid Rs. Rs adds what Rust should have had if it were designed for machines that never reboot.
Abstract
Rs is a minimal extension to the Rust programming language that adds seven domain primitives for building operating systems, blockchain nodes, and other long-lived deterministic systems. Rs is implemented as a patch to rustc (~4000 lines). Any valid Rust program compiles under Rs without modification. Rs adds compile-time guarantees that Rust cannot express: bounded async deadlines, MMIO safety without unsafe, deterministic computation, content-addressed types, epoch-scoped state, cell-based modularity, and region-based ownership.
The file extension is .rs. The edition identifier is rs. The compiler binary is rsc.
1. Design Principles
1.1 Strict Superset
Every valid Rust program is a valid Rs program. This is a hard constraint, not a goal. Compatibility is verified by compiling the top 1000 no_std crates from crates.io with rsc on every CI run.
Valid Rust ⊂ Valid Rs
Rs adds constructs. It never changes the meaning of existing Rust constructs.
1.2 Edition-Gated Restrictions
Rs introduces an rs edition. When active, certain Rust features are restricted or enhanced:
# Cargo.toml
[package]
edition = "rs"
In rs edition:
- Heap allocation primitives (
Box,Vec,String,HashMap) produce compile errors unless explicitly opted-in via#[allow(rs::heap)] dyn Traitproduces a compile error unless opted-in via#[allow(rs::dynamic_dispatch)]panic!()with unwinding produces a compile error; onlyabortmode is permitted- Floating point types (
f32,f64) are forbidden inside#[deterministic]functions - All
async fnmust have a deadline (see §3)
In standard Rust editions (2021, 2024), none of these restrictions apply. Rs extensions are still available but optional.
1.3 Zero New Keywords
Rs introduces zero new keywords. All extensions use:
- Attributes (
#[register],#[deterministic],#[epoch],#[content_addressed]) - Attribute-like syntax on existing keywords (
async(duration)) - Macro-like declarations (
cell! { })
This ensures no conflict with any existing or future Rust syntax.
2. Typed Registers (MMIO without Unsafe)
2.1 Problem
Every Rust OS kernel uses unsafe for hardware register access. A typical kernel has hundreds to thousands of unsafe { write_volatile(...) } blocks scattered across driver code. Each one is a potential source of memory corruption if the address, width, or access pattern is wrong.
2.2 Solution
Rs introduces the #[register] attribute, which declares a memory-mapped I/O register as a typed, compiler-verified construct.
2.3 Syntax
#[register(base = 0x23B10_0000, bank_size = 0x1000)]
mod aic {
/// Interrupt controller enable register
#[reg(offset = 0x010, access = "rw")]
pub struct Enable {
#[field(bits = 0..1)]
pub enabled: bool,
#[field(bits = 1..5)]
pub target_cpu: u8,
#[field(bits = 5..7)]
pub mode: IrqMode,
// bits 7..32 are reserved (implicit, compiler warns if accessed)
}
/// Interrupt status register
#[reg(offset = 0x014, access = "ro")]
pub struct Status {
#[field(bits = 0..16)]
pub pending_irq: u16,
#[field(bits = 16..20)]
pub source: IrqSource,
}
/// Interrupt clear register
#[reg(offset = 0x018, access = "wo")]
pub struct Clear {
#[field(bits = 0..16)]
pub irq_mask: u16,
}
#[repr(u8)]
pub enum IrqMode {
Edge = 0,
Level = 1,
Hybrid = 2,
}
#[repr(u8)]
pub enum IrqSource {
Timer = 0,
Gpio = 1,
Ipc = 2,
External = 3,
}
}
2.4 Generated Code
The compiler generates:
// Auto-generated by rsc — user never writes this
impl aic::Enable {
#[inline(always)]
pub fn read() -> Self {
let raw: u32 = unsafe {
core::ptr::read_volatile(0x23B10_0010 as *const u32)
};
Self {
enabled: (raw & 0x1) != 0,
target_cpu: ((raw >> 1) & 0xF) as u8,
mode: unsafe { core::mem::transmute(((raw >> 5) & 0x3) as u8) },
}
}
#[inline(always)]
pub fn write<F: FnOnce(&mut Self)>(f: F) {
let mut val = Self::default();
f(&mut val);
let raw: u32 = (val.enabled as u32)
| ((val.target_cpu as u32 & 0xF) << 1)
| ((val.mode as u32 & 0x3) << 5);
unsafe {
core::ptr::write_volatile(0x23B10_0010 as *mut u32, raw);
}
}
#[inline(always)]
pub fn modify<F: FnOnce(&mut Self)>(f: F) {
let mut val = Self::read();
f(&mut val);
let raw: u32 = /* ... pack fields ... */;
unsafe {
core::ptr::write_volatile(0x23B10_0010 as *mut u32, raw);
}
}
}
// Status is read-only: no write() or modify() generated
// Clear is write-only: no read() or modify() generated
2.5 Compile-Time Guarantees
| Check | Example Error |
|---|---|
| Read from write-only register | error[RS001]: register aic::Clear is write-only |
| Write to read-only register | error[RS002]: register aic::Status is read-only |
| Field exceeds register width | error[RS003]: field target_cpu (bits 1..5) exceeds u32 width |
| Field value exceeds bit range | error[RS004]: value 20 does not fit in 4-bit field target_cpu |
| Overlapping field bits | error[RS005]: fields enabled and target_cpu overlap at bit 1 |
| Enum variant exceeds field width | error[RS006]: IrqMode has 3 variants but field mode is 2 bits (max 4) |
| Address outside declared bank | error[RS007]: offset 0x2000 exceeds bank_size 0x1000 |
2.6 Usage
fn configure_interrupts() {
// Fully safe — no unsafe anywhere in user code
aic::Enable::write(|r| {
r.enabled = true;
r.target_cpu = 0;
r.mode = IrqMode::Edge;
});
let status = aic::Status::read();
if status.pending_irq > 0 {
aic::Clear::write(|r| {
r.irq_mask = status.pending_irq;
});
}
// Compile error: Status is read-only
// aic::Status::write(|r| { r.pending_irq = 0; });
}
2.7 Unsafe Accounting
The unsafe blocks exist only inside compiler-generated code. They are:
- Exactly 2 per register (one
read_volatile, onewrite_volatile) - Generated from verified attribute metadata
- Not visible to or writable by the user
- Auditable in compiler source (~200 lines of codegen)
User-facing code contains zero unsafe.
2.8 Fallback Compatibility
In standard Rust mode (non-Rs edition), #[register] can be implemented as a proc-macro crate that generates the same code. This means Rs register declarations are valid Rust with the right dependency — they just lack the compiler-level verification.
3. Bounded Async
3.1 Problem
Rust's async fn creates futures with no deadline. A forgotten .await on a network read can block a task forever. In OS kernels and blockchain nodes, this is not a bug — it is a liveness failure that can cost real money (slashing) or crash a system.
Existing workarounds (tokio::time::timeout()) are opt-in and forgettable. They are library-level, not language-level.
3.2 Solution
Rs extends async with an optional deadline parameter.
3.3 Syntax
// Standard Rust async — still valid, still works
async fn standard_function() -> Result<()> {
// ...
}
// Rs bounded async — deadline is part of the function signature
async(100ms) fn read_block(lba: u64) -> Result<Block> {
let data = device.read(lba).await; // .await inherits 100ms deadline
Ok(Block::from(data))
}
// Duration expressions allowed
async(Duration::from_secs(5)) fn sync_state() -> Result<()> {
// ...
}
// Constant expressions allowed
const CONSENSUS_TIMEOUT: Duration = Duration::from_millis(500);
async(CONSENSUS_TIMEOUT) fn propose_block() -> Result<Block> {
// ...
}
3.4 Semantics
When async(D) fn foo() -> T is called:
- An internal timer starts with duration
D - Every
.awaitinside the function checks the remaining time - If the timer expires before the function returns, the future resolves to
Err(Rs::Timeout) - The return type is transparently wrapped:
async(D) fn foo() -> Tactually returnsResult<T, Rs::Timeout>at the future level
Nested calls:
async(200ms) fn outer() -> Result<()> {
// inner gets at most the REMAINING time of outer, not its own 100ms
// if outer has 50ms left, inner's effective deadline is 50ms
let result = inner().await;
Ok(())
}
async(100ms) fn inner() -> Result<Data> {
// ...
}
The effective deadline is min(own_deadline, caller_remaining). Deadlines propagate inward, never expand.
3.5 Rs Edition Enforcement
In edition = "rs":
// ERROR in rs edition: async fn without deadline
async fn unbounded() -> Result<()> {
//~^ error[RS101]: async functions must have a deadline in rs edition
//~| help: add a deadline: async(100ms) fn unbounded()
}
// OK: explicit opt-out for rare cases (must justify)
#[allow(rs::unbounded_async)]
async fn special_case() -> Result<()> {
// ...
}
In standard Rust editions, async(duration) is available but not required.
3.6 Compiler Implementation
The parser recognizes async ( <expr> ) as a bounded async marker. The desugaring:
// Source:
async(100ms) fn foo(x: u32) -> Result<Bar> {
let a = something().await;
Ok(a.into())
}
// Desugared to (approximately):
fn foo(x: u32) -> impl Future<Output = Result<Result<Bar>, rs::Timeout>> {
rs::runtime::with_deadline(Duration::from_millis(100), async move {
let a = something().await;
Ok(a.into())
})
}
Parser changes: ~200 lines. Desugaring: ~300 lines. Diagnostic messages: ~100 lines.
4. Deterministic Functions
4.1 Problem
Blockchain consensus requires that every node produces identical output for identical input. Rust does not guarantee this. Floating point operations can produce different results on different architectures. Integer overflow behavior depends on debug/release mode. Memory layout of structs is unspecified.
4.2 Solution
The #[deterministic] attribute marks functions that must produce identical results on all platforms.
4.3 Syntax
#[deterministic]
fn compute_rank(weights: &[FixedPoint<u128, 18>]) -> FixedPoint<u128, 18> {
let mut sum = FixedPoint::ZERO;
for w in weights {
sum = sum.checked_add(*w)?; // checked arithmetic required
}
sum
}
4.4 Compile-Time Checks
Inside a #[deterministic] function, the compiler rejects:
| Rejected Construct | Reason | Error Code |
|---|---|---|
f32, f64 types |
Non-deterministic across platforms | RS201 |
as casts involving floats |
Rounding is platform-dependent | RS202 |
| Raw pointer arithmetic | Addresses are non-deterministic | RS203 |
std::time::Instant |
Wall clock is non-deterministic | RS204 |
rand::* |
Randomness is non-deterministic | RS205 |
Unchecked arithmetic (+, -, *) |
Overflow behavior differs debug/release | RS206 |
HashMap iteration |
Order is non-deterministic | RS207 |
| Inline assembly | Platform-specific by definition | RS208 |
Calling non-#[deterministic] functions |
Transitivity requirement | RS209 |
4.5 What IS Allowed
FixedPoint<T, DECIMALS>(Rs built-in fixed-point type)checked_add,checked_mul,checked_sub,checked_divsaturating_*arithmeticBTreeMap,BTreeSet(deterministic iteration order)- Arrays, slices with deterministic indexing
- Other
#[deterministic]functions const fn(already deterministic)- All comparison and logical operations
4.6 Transitivity
Determinism is contagious upward and required downward:
#[deterministic]
fn outer() -> u64 {
inner() // OK only if inner() is also #[deterministic]
}
#[deterministic]
fn inner() -> u64 {
42
}
fn non_det() -> u64 {
outer() // OK: non-deterministic can call deterministic
}
4.7 Built-in FixedPoint Type
Rs provides a built-in fixed-point numeric type:
// FixedPoint<BaseType, DecimalPlaces>
type Rank = FixedPoint<u128, 18>; // 18 decimal places, u128 backing
let a: Rank = Rank::from_integer(42);
let b: Rank = Rank::from_raw(42_000_000_000_000_000_000u128); // 42.0
let c = a.checked_add(b).unwrap();
let d = a.checked_mul(b).unwrap();
// All operations are deterministic, checked, no floats
Compiler implementation: ~400 lines (lint pass + diagnostics).
5. Content-Addressed Types
5.1 Problem
In content-addressed systems, data is identified by its hash. Producing the hash requires canonical serialization — the same data must always serialize to the same bytes. Rust has no built-in concept of canonical serialization or content-derived identity.
5.2 Solution
The #[content_addressed] derive macro generates canonical serialization and a .cid() method.
5.3 Syntax
#[derive(ContentAddressed)]
struct Cyberlink {
from: Cid,
to: Cid,
agent: Address,
height: u64,
}
let link = Cyberlink { from: a, to: b, agent: alice, height: 100 };
let id: Cid = link.cid(); // Deterministic, canonical hash
// Two identical structs always produce the same CID
let link2 = Cyberlink { from: a, to: b, agent: alice, height: 100 };
assert_eq!(link.cid(), link2.cid());
5.4 Canonical Serialization Rules
The derived serializer follows strict rules:
- Fields are serialized in declaration order (not alphabetical, not random)
- Integers are serialized as little-endian fixed-width bytes
- Variable-length types (arrays) are prefixed with a u32 length
- No padding bytes between fields
- Enums serialized as discriminant (u32) + variant data
- Nested
ContentAddressedtypes are serialized as their CID (32 bytes), not expanded
Hash function: BLAKE3 (256-bit output, producing a CID).
5.5 Compile-Time Checks
| Check | Error |
|---|---|
| Field type not serializable | error[RS301]: type MyOpaqueType does not implement CanonicalSerialize |
Contains f32/f64 |
error[RS302]: floating point types are not canonically serializable |
| Contains raw pointers | error[RS303]: pointers cannot be content-addressed |
Contains HashMap |
error[RS304]: HashMap has non-deterministic serialization; use BTreeMap |
5.6 Implementation
Implemented as a proc-macro (no compiler changes required). Works in both standard Rust and Rs editions. ~500 lines.
6. Epoch-Scoped State
6.1 Problem
Long-running systems accumulate state. A variable set in block N might still be non-zero in block N+1000 because someone forgot to clear it. This is a source of non-determinism and state leaks.
6.2 Solution
The #[epoch] attribute marks state that is automatically reset at epoch boundaries.
6.3 Syntax
#[epoch]
static PENDING_TXS: Mutex<BoundedVec<Transaction, 10_000>> =
Mutex::new(BoundedVec::new());
#[epoch]
static VOTES_THIS_ROUND: AtomicU32 = AtomicU32::new(0);
fn process_transaction(tx: Transaction) {
PENDING_TXS.lock().push(tx);
// At epoch boundary, PENDING_TXS is automatically reset to empty
// No manual cleanup required. Forgetting is impossible.
}
6.4 Semantics
The runtime calls EpochState::reset() at the beginning of each epoch (block). This is injected by the cell infrastructure, not by user code.
// Compiler generates this trait impl for every #[epoch] static:
impl EpochReset for BoundedVec<Transaction, 10_000> {
fn reset(&mut self) {
self.clear();
}
}
// At epoch boundary (generated by cell! macro):
fn __epoch_reset() {
PENDING_TXS.lock().reset();
VOTES_THIS_ROUND.store(0, Ordering::SeqCst);
}
6.5 Compile-Time Checks
In edition = "rs", accessing an #[epoch] variable outside of an epoch context is an error:
#[epoch]
static COUNTER: AtomicU64 = AtomicU64::new(0);
// OK: inside a cell function (has epoch context)
cell! {
name: MyCell,
pub fn tick(&self) {
COUNTER.fetch_add(1, Ordering::SeqCst); // OK
}
}
// ERROR: outside cell context
fn standalone() {
COUNTER.fetch_add(1, Ordering::SeqCst);
//~^ error[RS401]: #[epoch] state accessed outside of cell context
}
Implementation: ~300 lines (attribute handling + lint pass).
7. Cell Declarations
7.1 Problem
Operating system modules need: a private state, a public interface, a resource budget, health monitoring, hot-swap capability, and state migration between versions. Rust has none of these as a first-class concept. Crates provide modularity but not lifecycle management.
7.2 Solution
The cell! macro declares a self-contained, hot-swappable OS module.
7.3 Syntax
cell! {
name: Consensus,
version: 3,
budget: 500ms,
heartbeat: 1s,
state {
validators: BTreeMap<Address, StakeAmount>,
current_round: u64,
votes: BoundedVec<Vote, MAX_VALIDATORS>,
}
// Epoch-scoped state (auto-reset each block)
epoch_state {
round_votes: BoundedVec<Vote, MAX_VALIDATORS>,
proposed_block: Option<Block>,
}
// Public interface — other cells can call these
pub fn propose_block(&self, txs: &[Transaction]) -> Result<Block> {
// ...
}
pub async(200ms) fn vote(&mut self, block: &Block) -> Result<Vote> {
// ...
}
pub fn validator_set(&self) -> &BTreeMap<Address, StakeAmount> {
&self.state.validators
}
// Private functions — internal to cell
fn verify_proposer(&self, proposer: Address) -> bool {
self.state.validators.contains_key(&proposer)
}
// State migration from previous version
migrate from v2 {
validators: old.validators,
current_round: old.current_round,
votes: BoundedVec::new(), // new field in v3
}
}
7.4 Generated Code
The cell! macro generates:
// 1. State struct
pub struct ConsensusState {
validators: BTreeMap<Address, StakeAmount>,
current_round: u64,
votes: BoundedVec<Vote, MAX_VALIDATORS>,
}
// 2. Epoch state struct (with auto-reset)
#[epoch]
pub struct ConsensusEpochState {
round_votes: BoundedVec<Vote, MAX_VALIDATORS>,
proposed_block: Option<Block>,
}
// 3. Cell struct wrapping both
pub struct Consensus {
state: ConsensusState,
epoch_state: ConsensusEpochState,
}
// 4. Cell trait implementation
impl Cell for Consensus {
const NAME: &'static str = "Consensus";
const VERSION: u32 = 3;
const BUDGET: Duration = Duration::from_millis(500);
const HEARTBEAT: Duration = Duration::from_secs(1);
fn health_check(&self) -> HealthStatus { /* ... */ }
fn reset_epoch_state(&mut self) { self.epoch_state.reset(); }
}
// 5. Migration from v2
impl MigrateFrom<ConsensusStateV2> for ConsensusState {
fn migrate(old: ConsensusStateV2) -> Self {
Self {
validators: old.validators,
current_round: old.current_round,
votes: BoundedVec::new(),
}
}
}
// 6. Public interface methods (impl block)
impl Consensus {
pub fn propose_block(&self, txs: &[Transaction]) -> Result<Block> { /* ... */ }
pub async(200ms) fn vote(&mut self, block: &Block) -> Result<Vote> { /* ... */ }
pub fn validator_set(&self) -> &BTreeMap<Address, StakeAmount> { /* ... */ }
fn verify_proposer(&self, proposer: Address) -> bool { /* ... */ }
}
// 7. Metadata for introspection
impl CellMetadata for Consensus {
fn interface() -> &'static [FunctionSignature] {
&[
FunctionSignature { name: "propose_block", args: &["&[Transaction]"], ret: "Result<Block>" },
FunctionSignature { name: "vote", args: &["&Block"], ret: "Result<Vote>", deadline: Some(200) },
FunctionSignature { name: "validator_set", args: &[], ret: "&BTreeMap<Address, StakeAmount>" },
]
}
}
7.5 Hot-Swap Protocol
Epoch N Epoch N+1 Epoch N+2
┌──────┐ ┌──────────┐ ┌──────┐
│Cell │ drain │Cell v2 │ │Cell │
│v1 │───────>│loading + │────>│v2 │
│active│ │migration │ │active│
└──────┘ └──────────┘ └──────┘
state transfer
via MigrateFrom
- Governance approves new cell binary
- Current epoch completes normally
- At epoch boundary: old cell's state is serialized
MigrateFrom::migrate()transforms state to new version- New cell is initialized with migrated state
- New cell starts processing next epoch
- Old cell binary is unloaded
Total downtime: zero. Migration happens in the gap between epochs.
Implementation: proc-macro, ~2000 lines.
8. Owned Regions
8.1 Problem
Rust's borrow checker is designed for complex ownership graphs: multiple references with different lifetimes, interior mutability, self-referential structs. This complexity exists to support general-purpose programming. An OS kernel for a blockchain node doesn't need most of it.
8.2 Solution
In edition = "rs", the compiler enforces a simpler ownership model through lints. This is not a language change — it's a restriction.
8.3 Restrictions
#![edition = "rs"]
// FORBIDDEN (unless opted-in):
let x = Box::new(42);
//~^ error[RS501]: heap allocation forbidden in rs edition
//~| help: use a stack value or Arena<T, N>
let v = Vec::new();
//~^ error[RS502]: growable collections forbidden in rs edition
//~| help: use BoundedVec<T, N> with compile-time capacity
let s = String::from("hello");
//~^ error[RS503]: heap-allocated strings forbidden in rs edition
//~| help: use &str or ArrayString<N>
let d: Box<dyn Trait> = ...;
//~^ error[RS504]: dynamic dispatch forbidden in rs edition
//~| help: use generics or enum dispatch
let r = Arc::new(data);
//~^ error[RS505]: reference counting forbidden in rs edition
//~| help: use cell-owned state or bounded channels
// ALLOWED:
let a: [u8; 1024] = [0; 1024]; // stack allocation
let arena: Arena<Transaction, 10_000> = Arena::new(); // typed arena, compile-time sized
let bv: BoundedVec<u8, 256> = BoundedVec::new(); // bounded, no heap
let s: ArrayString<64> = ArrayString::from("hello"); // fixed-capacity string
8.4 Arena Allocator
Rs provides a built-in arena type for cases where dynamic-count-but-bounded allocation is needed:
// Arena with compile-time maximum capacity
let arena: Arena<Transaction, 10_000> = Arena::new();
// Allocate returns Option — None if arena is full
let tx: &mut Transaction = arena.alloc(Transaction::default())?;
// All allocations freed when arena goes out of scope
// No individual deallocation — this is by design
// No fragmentation, no use-after-free, no leaks
8.5 Explicit Opt-In
For interfacing with existing Rust crates that use heap allocation:
#[allow(rs::heap)]
mod legacy_compat {
use some_crate::SomeType; // This crate uses Vec internally
// OK: heap restrictions lifted in this module
}
Implementation: ~500 lines (lint pass checking for specific type paths).
9. Rs Standard Library Extensions
9.1 rs::fixed_point
use rs::fixed_point::FixedPoint;
type Amount = FixedPoint<u128, 18>; // 18 decimal places
let a = Amount::from_integer(100);
let b = Amount::from_decimal(3, 14); // 3.14
let c = a.checked_mul(b).unwrap(); // 314.00...
let d = a.checked_div(b).unwrap(); // 31.847...
// All operations: checked_add, checked_sub, checked_mul, checked_div
// All deterministic, all return Option (no panics)
9.2 rs::bounded
use rs::bounded::{BoundedVec, BoundedMap, ArrayString};
let mut v: BoundedVec<u8, 256> = BoundedVec::new();
v.try_push(42)?; // Returns Err if full
let mut m: BoundedMap<Key, Value, 1000> = BoundedMap::new();
m.try_insert(k, v)?; // Returns Err if full
let s: ArrayString<64> = ArrayString::try_from("hello")?;
9.3 rs::channel
use rs::channel::{bounded_channel, Sender, Receiver};
// Wait-free bounded MPMC channel
let (tx, rx) = bounded_channel::<Transaction>(1000);
// Non-blocking send
match tx.try_send(transaction) {
Ok(()) => { /* sent */ }
Err(Full(tx)) => { /* channel full, backpressure */ }
}
// Bounded receive (with deadline from async context)
let msg = rx.recv().await; // Inherits caller's deadline
9.4 rs::cid
use rs::cid::Cid;
let data = b"hello world";
let cid = Cid::from_bytes(data); // BLAKE3 hash
// Cid is Copy, 32 bytes, comparable, hashable
let map: BoundedMap<Cid, Data, 10_000> = BoundedMap::new();
9.5 rs::arena
use rs::arena::Arena;
let arena: Arena<MyStruct, 5000> = Arena::new();
let item: &mut MyStruct = arena.alloc(MyStruct::new())?;
// Arena tracks count, provides iteration
assert!(arena.count() <= 5000);
for item in arena.iter() { /* ... */ }
// All freed on drop — no individual deallocation
10. Compiler Implementation Plan
10.1 Architecture
┌──────────────────────────────────────────────┐
│ rsc │
│ (Rs Compiler) │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ rustc (forked) │ │
│ │ │ │
│ │ ┌────────────┐ ┌───────────────┐ │ │
│ │ │ Parser │ │ Rs Parser │ │ │
│ │ │ (unchanged)│ │ Extension │ │ │
│ │ │ │ │ async(dur) │ │ │
│ │ │ │ │ ~200 lines │ │ │
│ │ └─────┬──────┘ └──────┬────────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ HIR / MIR │ │ │
│ │ │ (unchanged) │ │ │
│ │ └─────────────┬──────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ Lint Passes │ │ │
│ │ │ ┌──────────────────────────┐ │ │ │
│ │ │ │ Rs Edition Lints │ │ │ │
│ │ │ │ - no heap (~200 lines) │ │ │ │
│ │ │ │ - no dyn (~50 lines) │ │ │ │
│ │ │ │ - no float in det │ │ │ │
│ │ │ │ (~300 lines) │ │ │ │
│ │ │ │ - bounded async check │ │ │ │
│ │ │ │ (~200 lines) │ │ │ │
│ │ │ └──────────────────────────┘ │ │ │
│ │ └─────────────┬──────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ Codegen │ │ │
│ │ │ ┌──────────────────────────┐ │ │ │
│ │ │ │ Register MMIO codegen │ │ │ │
│ │ │ │ (~800 lines) │ │ │ │
│ │ │ │ Bounded async desugar │ │ │ │
│ │ │ │ (~300 lines) │ │ │ │
│ │ │ └──────────────────────────┘ │ │ │
│ │ └─────────────┬──────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ LLVM Backend (unchanged) │ │ │
│ │ └────────────────────────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Rs Proc-Macros (standard crates) │ │
│ │ - #[derive(ContentAddressed)] 500L │ │
│ │ - cell! { } macro 2000L │ │
│ │ - #[epoch] handling 300L │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Rs Standard Library │ │
│ │ - rs::fixed_point 800L │ │
│ │ - rs::bounded 600L │ │
│ │ - rs::channel 500L │ │
│ │ - rs::cid 300L │ │
│ │ - rs::arena 400L │ │
│ └──────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────┘
10.2 Line Count Breakdown
| Component | Location | Lines | Nature |
|---|---|---|---|
async(dur) parser extension |
rustc fork | 200 | Compiler patch |
| Bounded async desugaring | rustc fork | 300 | Compiler patch |
| Register MMIO codegen | rustc fork | 800 | Compiler patch |
| Rs edition lint: no heap | rustc fork | 200 | Compiler patch |
| Rs edition lint: no dyn | rustc fork | 50 | Compiler patch |
#[deterministic] lint pass |
rustc fork | 400 | Compiler patch |
| Bounded async enforcement lint | rustc fork | 200 | Compiler patch |
| Rs diagnostics and error messages | rustc fork | 300 | Compiler patch |
| Compiler patch subtotal | 2,450 | ||
#[derive(ContentAddressed)] |
proc-macro crate | 500 | Standard Rust |
cell! macro |
proc-macro crate | 2,000 | Standard Rust |
#[epoch] attribute |
proc-macro crate | 300 | Standard Rust |
| Proc-macro subtotal | 2,800 | ||
rs::fixed_point |
library crate | 800 | Standard Rust |
rs::bounded |
library crate | 600 | Standard Rust |
rs::channel |
library crate | 500 | Standard Rust |
rs::cid |
library crate | 300 | Standard Rust |
rs::arena |
library crate | 400 | Standard Rust |
| Library subtotal | 2,600 | ||
| Total | ~7,850 |
The actual rustc patch is ~2,450 lines. Everything else is standard Rust crates that work with both rsc and rustc.
10.3 Build Pipeline
# Rs compiler is a patched rustc
$ git clone https://github.com/AnyOrganization/rust.git rsc
$ cd rsc
$ git apply rs-compiler.patch # ~2,450 lines
$ ./x.py build
# Compiles any .rs file
$ rsc my_program.rs # standard Rust mode
$ rsc --edition rs my_program.rs # Rs mode with all checks
# Or via Cargo
$ cargo +rsc build # uses rsc as compiler
10.4 Compatibility Testing
CI runs three test suites:
- Rust test suite: the full rustc test suite must pass with rsc (zero regressions)
- Top 1000 no_std crates: compile with rsc to verify superset property
- Rs-specific tests: test all 7 primitives, all error codes, all edge cases
11. Migration Path
Phase 1: Library-Only (works today)
Before the compiler patch exists, all Rs concepts except two can be used as standard Rust crates:
| Primitive | Library Implementation | Limitation |
|---|---|---|
| Typed registers | rs-registers proc-macro |
No compiler verification of MMIO safety |
| Bounded async | rs-async with timeout wrapper |
Not enforced, opt-in |
| Deterministic functions | rs-deterministic proc-macro |
Partial: catches float but not all cases |
| Content-addressed types | rs-cid derive macro |
Full functionality |
| Epoch-scoped state | rs-epoch attribute macro |
No cross-cell enforcement |
| Cell declarations | rs-cell proc-macro |
Full functionality |
| Owned regions | rs-lint clippy plugin |
Advisory warnings, not errors |
This means CyberOS development can start immediately using standard Rust with Rs libraries.
Phase 2: Compiler Patch
Apply the ~2,450 line patch to rustc. All library-based Rs code continues to work. Compiler now also enforces:
- MMIO safety at compile time
- Bounded async as requirement in Rs edition
- Deterministic function purity
- Heap/dyn restrictions in Rs edition
Phase 3: Upstream
Propose individual Rs features as Rust RFCs where appropriate:
#[deterministic]has general value beyond CyberOS- Bounded async could benefit any reliability-critical Rust code
- Typed registers would benefit the entire embedded Rust ecosystem
Features that are too domain-specific remain in the Rs fork.
12. Example: Complete CyberOS Cell in Rs
#![edition = "rs"]
use rs::prelude::*;
// Hardware register for network DMA
#[register(base = 0x4000_0000, bank_size = 0x100)]
mod net_dma {
#[reg(offset = 0x00, access = "rw")]
pub struct Control {
#[field(bits = 0..1)]
pub enabled: bool,
#[field(bits = 1..2)]
pub interrupt_on_complete: bool,
}
#[reg(offset = 0x04, access = "wo")]
pub struct TxDescriptor {
#[field(bits = 0..32)]
pub address: u32,
}
#[reg(offset = 0x08, access = "ro")]
pub struct Status {
#[field(bits = 0..1)]
pub tx_complete: bool,
#[field(bits = 1..2)]
pub error: bool,
}
}
// Content-addressed data structure
#[derive(ContentAddressed, BorshSerialize, Clone)]
pub struct Cyberlink {
pub from: Cid,
pub to: Cid,
pub agent: Address,
pub height: u64,
}
// A complete CyberOS cell
cell! {
name: KnowledgeGraph,
version: 1,
budget: 1500ms,
heartbeat: 1s,
state {
links: BoundedMap<Cid, Cyberlink, 10_000_000>,
agent_links: BoundedMap<Address, BoundedVec<Cid, 100_000>, 1_000_000>,
link_count: u64,
}
epoch_state {
new_links_this_epoch: BoundedVec<Cyberlink, 50_000>,
}
/// Add a cyberlink to the knowledge graph.
/// Deterministic: same input always produces same state change.
#[deterministic]
pub fn add_cyberlink(
&mut self,
from: Cid,
to: Cid,
agent: Address,
) -> Result<Cid> {
let link = Cyberlink {
from, to, agent,
height: self.current_epoch(),
};
let cid = link.cid();
self.state.links.try_insert(cid, link.clone())
.map_err(|_| Error::GraphFull)?;
self.state.agent_links
.entry(agent)
.or_default()
.try_push(cid)
.map_err(|_| Error::AgentLimitReached)?;
self.epoch_state.new_links_this_epoch.try_push(link)
.map_err(|_| Error::EpochFull)?;
self.state.link_count = self.state.link_count
.checked_add(1)
.ok_or(Error::Overflow)?;
Ok(cid)
}
/// Query links from a CID. Bounded async — 50ms max.
pub async(50ms) fn get_outlinks(&self, cid: Cid) -> Result<Vec<Cyberlink>> {
// Vec allowed here because it's a query return, not persistent state
// (could also use BoundedVec if strict)
#[allow(rs::heap)]
let mut results = Vec::new();
for (_, link) in self.state.links.iter() {
if link.from == cid {
results.push(link.clone());
}
}
Ok(results)
}
/// Get count of links created this epoch.
pub fn epoch_link_count(&self) -> usize {
self.epoch_state.new_links_this_epoch.len()
}
migrate from v0 {
links: old.links,
agent_links: BoundedMap::new(), // new in v1, rebuild from links
link_count: old.links.len() as u64,
}
}
This file:
- Is valid Rs (compiles with
rsc --edition rs) - Is almost valid standard Rust (compiles with
rustcif Rs proc-macros are available, minusasync(50ms)syntax) - Has zero
unsafe - Has compile-time MMIO verification
- Has determinism guarantees on state transitions
- Has bounded async on all queries
- Has content-addressed data by default
- Has auto-resetting epoch state
- Has hot-swap capability with state migration
- Can be generated by any LLM that knows Rust
13. Summary
Rs is not a new language. It is Rust with seven additions for building systems that never reboot:
| # | Primitive | Compiler Change | Library | Key Guarantee |
|---|---|---|---|---|
| 1 | Typed Registers | 800L codegen | — | MMIO without unsafe |
| 2 | Bounded Async | 500L parse+desugar | — | No unbounded waits |
| 3 | Deterministic Fns | 400L lint | — | Same output everywhere |
| 4 | Content-Addressed | — | 500L proc-macro | Identity from content |
| 5 | Epoch State | — | 300L proc-macro | No state leaks |
| 6 | Cells | — | 2000L proc-macro | Hot-swap + lifecycle |
| 7 | Owned Regions | 250L lint | — | No heap, no leaks |
Total compiler patch: ~2,450 lines.
Total library code: ~5,400 lines.
Compatibility: 100% with existing Rust.
File extension: .rs.
Any Rust programmer can write Rs. Any LLM trained on Rust can generate Rs. Any no_std crate works with Rs. The ecosystem is not forked — it is extended.
Rs is what Rust becomes when you optimize for machines that participate in collective intelligence rather than machines that run desktop applications.