Runtime Evaluator

The tswift runtime is a tree-walking interpreter that walks the typed AST produced by the frontend and evaluates it into side effects and output.


Core loop

The evaluator is an eval(node, env) -> Completion function:

Every Swift statement and expression is handled by pattern-matching node.kind.


SwiftValue

The central type of the runtime is SwiftValue, a Rust enum covering every value a Swift program can produce:

enum SwiftValue {
    Int(i64),
    Float(f64),
    Bool(bool),
    String(Rc<String>),           // CoW via Rc
    Nil,
    Optional(Option<Box<Self>>),
    Tuple(Vec<(Option<String>, Self)>),
    Array(Rc<RefCell<Vec<Self>>>), // CoW
    Dictionary(Rc<RefCell<...>>),
    Set(Rc<RefCell<...>>),
    Struct(Rc<RefCell<StructObject>>),
    Class(Rc<RefCell<ClassObject>>), // ARC via Rc
    Enum(EnumValue),
    Closure(Rc<ClosureObject>),
    Function(FunctionValue),
    Metatype(SwiftType),
    // … more
}

Memory model

    
flowchart LR
  subgraph Swift
      cows["Value types\n(struct, enum, String, Array)\nCopy on assign/pass"]
      refs["Reference types\n(class)\nARC retain/release"]
      wks["weak / unowned\nZeroing references"]
  end
  subgraph Rust
      rcc["Rc::clone + Rc::make_mut\n(copy-on-write)"]
      rcrc["Rc<RefCell<Object>>\n(ARC = ref count)"]
      wk["rc::Weak\n.upgrade() → None after drop"]
  end
  cows --> rcc
  refs --> rcrc
  wks  --> wk
  style Swift fill:#1e1e27,stroke:#ff6b35
  style Rust  fill:#1e1e27,stroke:#a855f7

  
Swift memory semantics mapped to Rust mechanisms

Value types (struct, enum, String, Array)

All value types are wrapped in Rc<T>. Assignment or function call clones the Rc (cheap pointer bump). Mutation calls Rc::make_mut, which performs the actual deep copy only if there is more than one strong reference — true copy-on-write.

Reference types (class)

Class instances are Rc<RefCell<ClassObject>>. Retain is clone; release is drop (when the last strong reference is dropped, deinit runs via Rust’s Drop trait). This gives us deterministic, prompt finalization matching Swift’s ARC.

Weak references

weak var is implemented with rc::Weak<RefCell<ClassObject>>. Calling .upgrade() returns None when the strong count reaches zero — exactly Swift’s zeroing weak reference semantics.


Closures

Closures capture their environment by cloning the Rc<RefCell<Env>> of their definition scope. This gives:


Concurrency (async/await, actors)

tswift implements Swift concurrency on a single-threaded cooperative executor (see ADR-0005):

Correctness (final values, task structure) is faithful. Preemptive scheduling interleaving may differ from native Swift because the executor is cooperative.


Error handling

throw produces a Completion::Throw(SwiftValue). do/catch catches it by pattern-matching against the error value. try? maps Throw to Normal(Optional::None). try! panics (traps) on Throw.


Runtime execution tiers

tswift runs the same tree-walking interpreter on every platform. There is no ahead-of-time compilation, no JIT, and no bytecode — each tier lexes, parses, type-resolves, and then interprets the AST. Tiers differ only in the host capabilities they expose and in whether repeated runs reuse a cached analysis; the execution strategy is identical everywhere.

Tier Execution Warm-start caching Host services (files / defaults / database)
Native CLI Tree-walking interpreter None — process-per-run Real: OS filesystem, JSON-backed defaults, SQLite
Web (wasm) Tree-walking interpreter In-memory analysis cache for byte-identical re-runs Virtual: in-memory filesystem, browser storage, SQLite (wasm)
iOS embed Tree-walking interpreter None wired (same pattern available) Real: Foundation filesystem, real SQLite

Where startup time goes

Profiling a cold run splits it into four phases — installing the standard library and framework builtins, analyzing the boilerplate prelude, analyzing user code, and executing. On every platform, execution dominates: roughly 90% or more of total wall time. Installing builtins and analyzing source together stay under ~1.6 ms even for a 1,000-line program, while execution ranges from several to tens of milliseconds. Because the interpreter is the wall, startup is never the bottleneck for a non-trivial program, and there is no separate “compile” step to eliminate.

On the web, re-running byte-identical source reuses the prior analysis, trimming a few tenths of a millisecond up to ~1.6 ms of re-analysis. This is pure latency caching — it never changes output — and it is not compilation.


Explore further