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 Trait produces a compile error unless opted-in via #[allow(rs::dynamic_dispatch)]
  • panic!() with unwinding produces a compile error; only abort mode is permitted
  • Floating point types (f32, f64) are forbidden inside #[deterministic] functions
  • All async fn must 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, one write_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:

  1. An internal timer starts with duration D
  2. Every .await inside the function checks the remaining time
  3. If the timer expires before the function returns, the future resolves to Err(Rs::Timeout)
  4. The return type is transparently wrapped: async(D) fn foo() -> T actually returns Result<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_div
  • saturating_* arithmetic
  • BTreeMap, 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:

  1. Fields are serialized in declaration order (not alphabetical, not random)
  2. Integers are serialized as little-endian fixed-width bytes
  3. Variable-length types (arrays) are prefixed with a u32 length
  4. No padding bytes between fields
  5. Enums serialized as discriminant (u32) + variant data
  6. Nested ContentAddressed types 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
  1. Governance approves new cell binary
  2. Current epoch completes normally
  3. At epoch boundary: old cell's state is serialized
  4. MigrateFrom::migrate() transforms state to new version
  5. New cell is initialized with migrated state
  6. New cell starts processing next epoch
  7. 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:

  1. Rust test suite: the full rustc test suite must pass with rsc (zero regressions)
  2. Top 1000 no_std crates: compile with rsc to verify superset property
  3. 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 rustc if Rs proc-macros are available, minus async(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.

Local Graph