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:
node— aNodefrom the typed ASTenv— the current lexical environment (aRc<RefCell<Env>>chain)Completion— the result of evaluation:Normal(value),Return(value),Throw(error),Break,Continue
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
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:
- Upvalues that outlive their defining scope — the
Envstays alive as long as the closure does. [weak self]capture lists — stored asWeakinside theClosureObject.@escaping— closures are alwaysRc-based and can freely escape.
Concurrency (async/await, actors)
tswift implements Swift concurrency on a single-threaded cooperative executor (see ADR-0005):
async fnwraps its body in a coroutine (viacorosensei).awaitsuspends the current coroutine and yields back to the executor.- The executor drains a ready queue, running tasks to completion at each
await. actorisolation is enforced by serializing method calls through the executor.Task,Task.detached,withTaskGroup,async letare all supported.
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.