Language Comparison · U vs the World

Every language made a choice.
U made all of them.

Ten languages, ten design philosophies, one consistent set of categories. Where each wins, where U wins, and why the tradeoffs exist.

🐍

Python

The reigning language of ML, scripting, and science. U is designed to be Python's natural successor.

Since1991 MemoryGC (reference counted) TypingDynamic + gradual hints Asyncasync/await (coloring) NullNone — no safety
01

Memory model

Python uses reference counting with a cycle collector. Everything is a heap object. Stack allocation doesn't exist as a concept. The GIL (Global Interpreter Lock) prevents true multi-core parallelism in CPython. Memory overhead per object is substantial — a simple integer is 28 bytes.

Python
# Every value heap-allocated
# GIL prevents true parallelism
x = 42           # 28 bytes on heap
arr = [1,2,3]    # list of heap objects
# No stack/heap distinction
U
// Stack by default (-R)
x: I = 42        // 8 bytes, stack
arr: [I] = [1,2,3] // contiguous, stack
heap: I+R = 42  // explicit heap opt-in
// No GIL — cactus stack + biased ARC
02

Null safety

Python's None can appear anywhere. No type system prevents AttributeError: 'NoneType' object has no attribute 'x'. Type hints help but aren't enforced at runtime. Optional[T] is just a hint.

Python
user = users.get(id)
# None if missing — no warning
print(user.name)  # 💥 AttributeError
U
user: User+N = users[id]
// +N forces you to handle absence
user ?? r => none  // must handle
print(user.name)   // safe — non-null
03

Async & concurrency

The coloring problem: async def functions can only be awaited from other async def functions. Making any function async requires rewriting every caller up the entire call chain. Python has three largely-incompatible concurrency models: asyncio, threading, multiprocessing. When await suspends, the entire call stack is serialised into a heap-allocated coroutine object — local variables that survive the suspension are captured in that closure. Stack frames are lost across suspensions.

Python — infection spreads up
# foo() needs to be async...
async def foo(config, threshold): ...

# ...so bar() must become async (infected)
async def bar():
    config    = load_config()  # local
    threshold = 42             # local
    # awaiting serialises config+threshold
    # into a heap closure before yielding
    result = await foo(config, threshold)

# ...so main() must become async (infected)
async def main():
    await bar()  # infected

asyncio.run(main())  # ceremony at every entrypoint
U — no infection, cactus retains parent
// foo() is async
a f foo(config: Config, threshold: I) -> S: ...

// bar(): NOT async — not infected
f bar()
    config    = load_config()  // on stack segment
    threshold = 42             // on stack segment
    // foo suspends → new cactus BRANCH
    // bar's segment stays alive
    // config+threshold reachable via parent ptr
    result = a foo(config, threshold)

// main(): NOT async — not infected
f main()
    bar()   // plain call — just works

How U eliminates coloring — the cactus stack. When a foo() suspends inside a plain function, U does not unwind the call stack. It creates a new branch of the cactus tree. The parent frame — its local variables, loop state, everything — stays alive in its stack segment, kept by a reference count on the parent pointer held by the child fiber. When the child completes, the count drops and the parent resumes. No heap closure. No serialised continuation. No refactoring cascade.

cactus_demo.u
// config and threshold live on parent stack segment
config    = load_config()   // -R, on stack
threshold = 42              // -R, on stack

// a foo() — if foo is sync: zero cost, same stack
// if foo suspends: new branch created
// parent segment stays alive via parent pointer ARC
result = a foo(config, threshold)

// Three concurrent branches — same parent segment
r1: S+A = a foo(config, threshold)  // branch 1
r2: S+A = a foo(config, threshold)  // branch 2
r3: S+A = a foo(config, threshold)  // branch 3
// config and threshold NOT copied — referenced via parent pointer
// No @escaping. No [weak self]. No Arc<Mutex<>>. No Sendable.
cactus_tree.txt
main() segment
  │  config = ..., threshold = 42

  ├── foo() branch 1   ← waiting on network
  ├── foo() branch 2   ← waiting on network
  └── foo() branch 3   ← waiting on network

Parent segment refcount = 3 (one per branch)
When branch completes: refcount--
When refcount == 0: parent resumes
No copies. No closures. O(1) per branch.
04

Type system

Python's type hints (PEP 484) are annotations only — ignored at runtime unless mypy or pyright is run separately. Duck typing means any object can be passed anywhere. No modifier algebra. No ownership. No compile-time guarantees of any kind.

Python — hints ignored at runtime
def process(items: list[Item]) -> None:
    items[0].count += 1  # mutates silently
# caller: mutates? async? throws? Unknown.
U
f process(items: [Item]+R+M) -> none
// +M: mutates  no +A: sync  no !: no throw
// Everything in the signature

Vectorization, GPU, exact arithmetic, autodiff. Python needs NumPy for SIMD vectorization, PyTorch or JAX for GPU tensors, the decimal or fractions module for exact arithmetic, and an autograd library for differentiation. In U these are built into the type and modifier system — no imports, no dtype systems, no separate compilation targets.

Python — four separate libraries
import numpy as np
import torch

# NumPy: separate dtype system
a = np.array([1.0, 2.0, 3.0], dtype=np.float32)
c = a + a          # element-wise — works

# PyTorch GPU: separate placement API
t = torch.tensor([1.0, 2.0, 3.0]).cuda()
w = torch.randn(512, 512, device="cuda")

# Exact: decimal module, manual everywhere
from decimal import Decimal
price = Decimal("1") / Decimal("3")  # 1/3

# Autodiff: requires grad tracking
x = torch.tensor(2.0, requires_grad=True)
y = x ** 3
y.backward()
x.grad  # 12.0 — but only for torch ops
U — modifiers, no imports
# +V: SIMD — compiler picks ISA (SSE/AVX/NEON)
a: [N32]+V = [1.0, 2.0, 3.0]
c = a + a                  // SIMD dispatch

# +R(GPU): GPU placement — same API, different loc
t: [N32]+R(GPU)    = [1.0, 2.0, 3.0]   // copy to GPU
w: [[N32]]+R(GPU)  = load_weights()     // 2D on GPU
c_gpu = t + t              // GPU kernel dispatched

# Q: exact rational — standard type, no import
price: Q = Q(1, 3)         // exactly 1/3
total = price * 99         // exactly 33 — symbolic

# Autodiff: standard library, any expression
x: N = 2.0
y = x ^ 3
dy_dx = Diff.grad(y, x)   // 12.0 — works on all N
Same code, three locations. [[N32]] on CPU, [[N32]]+V for SIMD, [[N32]]+R(GPU) on GPU. The operations (+, *, @, ^) are identical — the modifier routes dispatch. No .cuda(), no device=, no dtype conversion. The Q type carries exact rationals symbolically until =! forces materialisation — intermediate calculations never accumulate float error.
05

Error handling

Python uses exceptions — any code can throw anything, nothing is declared. try/except is a runtime scope. Happy path and error path are interleaved. No exhaustiveness checking.

Python — exception buried in try/except
def run(url):
    try:
        data = fetch(url)      # buried
        return process(data)   # buried
    except NetworkError: retry()
    except: pass  # swallows everything
U
f run(url: S) -> Result+A
    e.x([retry(3), log])
    data = fetch(url)  // clean happy path
    r => process(data)
06

Metaprogramming

Python decorators (@cache, @retry) apply at runtime — call overhead on every invocation, no type safety. __getattr__ and metaclasses are powerful but untyped. exec and eval exist. No compile-time execution equivalent to c f.

Python — runtime decorator overhead
@functools.lru_cache(maxsize=128)  # runtime
@retry(stop=stop_after_attempt(3)) # runtime
def fetch(url: str) -> str:
    return requests.get(url).text
# call overhead every invocation
U
fetch = c memo(c retry(3,
    http`GET {url: S}`
))
// c f: compile-time — zero runtime overhead
// cache key = function hash + typed inputs
07

Injection & templates

Python f-strings are untyped string interpolation. SQL injection requires ORMs or discipline. HTML injection requires escaping libraries. None of this is enforced by the type system. cursor.execute(f"SELECT * FROM users WHERE id = {id}") is a valid, dangerous pattern.

Python — injection possible
id = req.args["id"]
cursor.execute(f"SELECT * FROM u WHERE id={id}")
# id="0 OR 1=1" leaks entire table
U
id: I = req.params["id"]
sql`SELECT * FROM u WHERE id = {id}`()
// I slot: parameterised — never string-interpolated
08

Ergonomics

Python's syntax is genuinely elegant — clean indentation, readable English keywords, minimal ceremony. U's syntax is heavily inspired by Python and intentionally reads similarly. The -> return type annotation is borrowed directly from Python. One key difference: Python accumulated many ways to do things (format strings, async models, class decorators); U keeps one obvious way.

Python — 5 format styles, 3 async models
"x=%s" % x  /  "x={}".format(x)  /  f"x={x}"
# Three concurrent models: asyncio / threading / multiprocessing
# Type hints not enforced at runtime
U
"x={x}"  // one style, always typed
// One concurrent model: +A + cactus
// Types enforced at compile time, always

U wins

  • Stack allocation — no GC pauses
  • Null safety — compile-time enforced
  • Async without coloring
  • Injection safety — type system
  • Compile-time decorators — zero overhead
  • True parallelism — no GIL
  • One way to do things

Python wins

  • Ecosystem — NumPy, PyTorch, pandas
  • REPL-driven exploration
  • Fastest to prototype
  • Largest ML community
  • Decades of libraries
🟦

TypeScript

JavaScript with a type layer. The dominant language of the web. U is what TypeScript would be if designed from scratch for the agentic era.

Since2012 MemoryGC (V8 / JSC) TypingStructural, gradual Asyncasync/await (coloring) NullstrictNullChecks opt-in
01

Memory model

GC via V8's generational garbage collector. No stack allocation. Objects, closures, and async continuations all heap-allocate. Every await serializes the call stack to a heap-allocated Promise closure.

TypeScript — heap closure per await
async function bar() {
  const cfg = loadConfig()  // must survive await
  const r = await foo(cfg)  // cfg → heap Promise closure
}
U
f bar()                     // NOT async
    cfg    = load_config()  // on stack segment
    result = a foo(cfg)     // cactus branch
    // cfg: parent ptr, no heap closure
02

Null safety

TypeScript has strictNullChecks — disabled by default, must be opted in. Even then, any escapes all checks. JavaScript's undefined and null are distinct but often conflated. Prototype property access returns undefined silently.

TypeScript
// strictNullChecks must be enabled
// and can be bypassed with 'any'
const user: User | undefined = getUser(id)
user.name  // error (with strict)
(user as any).name  // bypasses — compiles
U
// -N is the default — non-null
// No 'any' escape hatch
user: User+N = getUser(id)
user.name  // must check first
user ?? r => none
user.name  // ✓ safe
03

Async & concurrency

Full coloring problem — async infects every caller. Two queues (macrotask / microtask) with real starvation risk: a long chain of awaits can starve I/O callbacks indefinitely. Every await serialises local variables into a heap Promise closure before yielding. No true threads — Web Workers require explicit message passing, no shared memory beyond SharedArrayBuffer.

TypeScript — heap closure per await
// foo() goes async...
async function foo(cfg: Config, n: number) {}

// ...bar must become async (infected)
async function bar() {
    const cfg = loadConfig()  // must survive await
    const n   = 42
    // cfg and n serialised into heap Promise closure
    const r = await foo(cfg, n)
}
// main must become async (infected)
async function main() { await bar() }
U — cactus, no infection
a f foo(cfg: Config, n: I) -> S: ...

f bar()                    // NOT async
    cfg = load_config()    // on bar's stack segment
    n   = 42               // on bar's stack segment
    // foo suspends → cactus branch
    // bar's segment stays: cfg, n reachable
    r = a foo(cfg, n)      // no closure needed

f main()
    bar()  // plain — not infected

Why U has no coloring — the cactus stack. When a foo() suspends, U creates a new branch on the cactus tree. The calling function's stack segment stays alive — its local variables reachable via a parent pointer held by the child fiber. When the child completes, the parent resumes. No heap closure, no continuation serialisation, no refactoring cascade. If foo is synchronous, a foo() is a zero-cost no-op — same call stack, zero overhead. This means you can always write a foo() defensively; the compiler handles both cases.

cactus_stack.u
// Calling function is NOT async — no annotation needed
f bar()
    config    = load_config()     // lives on bar's stack segment
    threshold = 42                // lives on bar's stack segment
    result = a foo(config, threshold)  // foo suspends:
    // ┌─ bar's segment ─────────────────┐  ← alive, refcount = 1
    // │  config, threshold              │
    // └──────────────────────────────────┘
    //        └── foo() branch  ← waiting, holds parent ptr
    // When foo() completes: refcount-- → bar resumes
    // config and threshold: NOT copied, NOT closed over
04

Type system

Structural typing — a strength for web APIs but means two unrelated types with the same shape are compatible. No ownership. No modifier algebra. readonly is shallow — nested objects are still mutable. Union types are powerful but complex; conditional types can become arcane.

TypeScript — structural: accident-prone
interface User    { name: string; age: number }
interface Product { name: string; age: number }
const p: Product = { name:'x', age:1 }
greet(p)  // TypeScript: ✓ (structural match)
U
d User    { name: S; age: I }
d Product { name: S; age: I }
p: Product = Product(name:'x', age:1)
greet(p)  // ✗ type error — nominal
05

Error handling

Untyped exceptions — anything can be thrown, nothing is declared. Caught values are typed unknown (since TS 4.0) and must be narrowed. No exhaustiveness on catch. Async errors require .catch() or try/await — separate patterns for same concept.

TypeScript — unknown in catch
try {
  const r = await fetch(url)
} catch (e: unknown) {
  if (e instanceof NetworkError) retry()
  else throw e  // rethrow unknown
}
U
f run(url: S) -> Data+A
    e.x([retry(3), log])
    data = fetch(url)      // happy path
    r => process(data)
06

Metaprogramming

TypeScript's type-level programming (conditional types, mapped types, template literal types) is powerful but operates only on types — no values. Decorators are stage-3 and primarily runtime. No compile-time execution. Code generation requires separate tooling.

TypeScript — types only, runtime decorators
// Type-level: no values
type NonNull = T extends null ? never : T
// Runtime decorator:
@Cache({ ttl: 60 }) fetch(url: string) {}  // call overhead
U
// c f: compile-time values AND types
fetch = c memo(c timeout(5000,
    http`GET {url: S}`
))  // zero runtime overhead
07

Injection & templates

Tagged template literals exist but are rarely typed safely. SQL injection is a discipline problem — no type system enforcement. XSS requires sanitisation libraries. `SELECT * FROM users WHERE id = ${id}` compiles fine and is dangerous.

TypeScript
// Compiles — dangerous
db.query(`SELECT * FROM users
          WHERE id = ${userId}`)
// userId = "0 OR 1=1" → leak
U
// Type error — S ≠ Sql
sql`SELECT * FROM users
    WHERE id = {userId: I}`()
// userId bound as parameter, not string
08

Ergonomics

TypeScript uses : T for type annotations — U now uses -> for function return types (aligned with Python/Rust/Swift). TS's structural typing makes APIs flexible; U's nominal-ish system with modifier algebra is more explicit. TypeScript's tooling — VSCode integration, Language Server — is best-in-class.

TypeScript — Promise ceremony
const [a,b,c] = await Promise.all([
  fetchA(), fetchB(), fetchC()
])
// null | undefined confusion everywhere
U
a: S+A = a fetch_a()
b: S+A = a fetch_b()
c: S+A = a fetch_c()
// +N for nullable. -N default. No undefined.

U wins

  • No GC pauses — ARC + cactus
  • Null safety on by default
  • No coloring — async ergonomics
  • SQL/HTML injection = type error
  • No any escape hatch
  • Compile-time metaprogramming
  • True parallelism

TypeScript wins

  • Runs everywhere JS runs
  • Massive npm ecosystem
  • Best-in-class IDE tooling
  • Structural typing for APIs
  • Gradual adoption path
🦀

Rust

The closest spiritual relative. Same safety goals, fundamentally different philosophy: Rust proves non-escape; U declares it.

Since2015 MemoryOwnership + borrow checker TypingNominal, strong, inferred Asyncasync/await (coloring, stackless) NullOption<T> — enforced
01

Memory model

Rust's borrow checker statically proves no aliased mutation exists. Ownership is tracked via lifetimes. Zero GC. The key difference from U: Rust proves non-escape via whole-program analysis; U requires you to declare it with -R. Rust's analysis catches mistakes; U's annotation eliminates the analysis entirely. Both produce safe code — different ergonomic costs.

Rust
// Borrow checker proves non-escape
// Shared mutable requires Arc<Mutex<T>>
let state = Arc::new(Mutex::new(Config::new()));
let state2 = Arc::clone(&state);
thread::spawn(move || {
    let mut s = state2.lock().unwrap();
    s.update(delta);
});
U
// Declare the fact — compiler reads it
// MVCC allows multiple concurrent writers
state: Config+R+M(MVCC) = Config()
snap = state.c()       // snapshot
state.update(delta)    // concurrent safe
02

Null safety

Rust's Option<T> is excellent — enforced, exhaustive pattern matching. U's T+N achieves the same result with lighter syntax and makes non-null the default (no annotation needed for the common case). Rust requires wrapping everything in Some(); U just uses values directly.

Rust — Option + unwrap or match
let user: Option = db.find(id);
user.unwrap().name  // panics if None
// Safe: if let Some(u) = user { u.name }
U
user: User+N = db.find(id)
user.name          // ✗ compile error
user ?? r => none  // handle
user.name          // ✓ safe
03

Async & concurrency

Rust async is stackless: the compiler transforms async fn into a state machine storing only variables that cross .await points. Lean — but with three costs. First, coloring: async fn infects every caller. Second, you cannot .await inside a non-async closure, so iterating a list and awaiting per item requires collecting futures then joining — the natural pattern is blocked. Third, async trait methods need Pin<Box<dyn Future>>.

Rust — can't await in iterator
async fn foo(cfg: &Config, n: u32) -> String { ... }

async fn bar() {
    let cfg = load_config();
    let n = 42u32;
    // CANNOT await inside non-async closure:
    // items.iter().for_each(|i| foo(&cfg,n).await) ✗
    // MUST collect futures then join:
    let futs: Vec<_> = items.iter()
        .map(|_| foo(&cfg, n)).collect();
    futures::join_all(futs).await;
}
// Async trait: requires Pin<Box<dyn Future+Send>>
U — await inside iterator is natural
a f foo(cfg: Config, n: I) -> S: ...

f bar()                    // NOT async
    cfg = load_config()    // on bar's segment
    n   = 42
    // await INSIDE iterator — just works
    items.x(item =>
        result = a foo(cfg, n)  // cactus branch
        store(result))
    // No join_all. No collect. No Pin.

Pin<Box<dyn Future>> exists because Rust's async state machine contains self-references and must not be moved in memory after polling begins. The cactus branch is a stack segment linked by a parent pointer — it doesn't move, needs no pinning, contains no self-references.

The cactus stack. When a foo() suspends, U creates a new branch. The caller's stack segment stays alive — its local variables reachable via a parent pointer held by the child fiber. When the child completes, the parent resumes. No heap closure. No serialisation. No infection. Writing a foo() where foo is synchronous is a zero-cost no-op.

cactus_stack.u
// bar() is NOT marked async
f bar()
    cfg = load_config()        // on bar's stack segment
    n   = 42
    result = a foo(cfg, n)    // foo suspends:
    // ┌ bar segment alive ──────────┐  refcount = 1
    // │  cfg, n                     │
    // └─────────────────────────────┘
    // └── foo branch ← waiting, holds parent ptr
    // foo completes → refcount-- → bar resumes
    // cfg and n were NEVER copied or closed over
04

Type system

Rust's type system is powerful: traits, generics with bounds, associated types, const generics. Lifetime parameters add a third dimension beyond types. The annotation burden can be substantial: Arc<Mutex<HashMap<String, Vec<Box<dyn Trait + Send + Sync>>>>> is real code. U's modifier algebra expresses ownership, mutability, and async in 8 short symbols.

Rust — annotation accumulation
// Real production type:
HashMap>>>>
// Lifetime on every borrowed reference
U
// 8 modifiers cover all of this:
{S: [Trait+R+M]+R}
// No Box, no dyn, no Send+Sync, no 'lifetime
05

Error handling

Rust's Result<T, E> with the ? operator is excellent — typed, exhaustive, propagating. U's ! ErrorType in the function signature with e.x() handlers achieves the same goals with zero happy-path noise: handlers are declared once as policy arrays, never interleaved with logic.

Rust — Result + ? per call
fn run(url: &str) -> Result {
    let raw  = fetch(url)?;  // ? per call
    let data = parse(raw)?;  // ? per call
    Ok(data)
}
U
f run(url: S) -> Data ! NetworkError
    raw  = fetch(url)   // happy path
    data = parse(raw)   // happy path
    r => data
06

Metaprogramming

Rust has two macro systems: declarative (macro_rules!) and procedural (#[proc_macro]). Both are powerful but separate from the type system — proc macros operate on token streams, not typed ASTs. Compilation time suffers. U's c f is the same language in a different execution domain — typed, composable, fast.

Rust — two macro systems, separate API
// Declarative:
macro_rules! vec { ($($x:expr),*) => ... }
// Proc macro: separate crate, TokenStream API
#[proc_macro] pub fn make(input: TokenStream) -> ...
U
// c f: same language, typed, generic:
c f retry[T](func: F(S)->T, n: I) -> F(S)->T
    r => url => [1..n].find(i => (
        result = func(url)
        result != none ? result : none))
// Types checked. Author writes error messages.
07

Injection & templates

Rust has no built-in template safety for SQL or HTML. Libraries like sqlx verify queries at compile time against a live database (requires a DB connection at compile time). U's sql\`\` tag validates against the schema loaded at compile time — same goal, cleaner mechanism, no live DB required.

Rust sqlx — needs live DB to compile
// sqlx: connects to DB during build
query!("SELECT * FROM users WHERE id=$1", id)
// No live DB available → build fails
// CI must provision a database
U
sql`SELECT * FROM users WHERE id={id: I}`()
// __schema() loads schema file — no live DB
// CI: schema.sql in repo is enough
08

Ergonomics

Rust's learning curve is famously steep — lifetimes, the borrow checker, and Pin require significant mental model investment. The reward is well-earned. U's approach — declare facts instead of proving them — has a shallower curve for the same safety. The cactus stack alone eliminates most lifetime concerns.

Rust — async trait + Pin ceremony
#[async_trait]
trait Handler: Send + Sync {
    async fn handle(&self) -> Result<(), Err>;
}  // => Pin> under hood
U
d Handler
    a f handle() -> none ! Error
// +A inferred. No Pin. No Box. No Send+Sync.

U wins

  • No coloring — async ergonomics
  • MVCC / CRDT policies — impossible in safe Rust
  • No lifetime annotations
  • No Pin, no Arc<Mutex<>>
  • Shallower learning curve
  • Built-in injection safety
  • Compile-time DSL engine

Rust wins

  • Mature ecosystem (crates.io)
  • Proven in production systems
  • WebAssembly first-class
  • Unsafe escape hatch when needed
  • Largest memory-safe community
🐹

Go

Closest to U in concurrency ergonomics. Goroutines solved coloring; U does the same and adds safety and frame minimisation.

Since2009 MemoryGC (tricolor mark-sweep) TypingStructural interfaces, simple AsyncGoroutines — no coloring Nullnil — runtime panics
01

Memory model

GC with periodic pauses. Goroutines save entire stacks on growth — no equivalent of +A to minimise what's preserved. Stack copying adds latency spikes under load. No per-value ownership or annotation.

Go — implicit escape, GC, stack copying
func make() *Result {
    r := &Result{val: 42}  // compiler: escapes to heap
    return r               // GC tracks it
}  // goroutine stacks copied on growth
U
f make() -> Result      // stack copy returned
f make() -> Result+R    // explicit heap
// Cactus segments: never copied, fixed size
02

Null safety

nil pointers cause runtime panics. No type system enforcement. Interface values can be nil. The zero value is a well-designed convention but still allows nil pointer dereference without compile-time warning.

Go — nil panic, interface nil trap
user := getUser(0)      // returns nil
fmt.Println(user.Name)  // nil panic
// Interface nil trap:
var err *MyError = nil
var e error = err  // e != nil!
U
user: User+N = get_user(0)
user.name          // ✗ compile error
user ?? r => none  // must handle
user.name          // ✓ safe
03

Async & concurrency

Goroutines solve coloring — any function can block. But goroutine stacks are copied on growth (not segmented), adding latency. No typed +A to tell the runtime "save only these values." WaitGroup ceremony for fan-out/fan-in. Channels are elegant; select statements are powerful.

Go
var wg sync.WaitGroup
results := make([]Config, len(keys))
for i, key := range keys {
    wg.Add(1)
    go func(i int, k string) {
        defer wg.Done()
        results[i] = lookup(k)
    }(i, key)
}
wg.Wait()
U
jobs: [Config+A] = keys.map(k => a lookup(k))
cfgs: [Config]    = jobs   // auto-resolve
// No WaitGroup. No closure index bug.
// Frame = only +A values (not full stack)
04

Type system

Go's type system is intentionally simple — structural interfaces, no generics until 1.18. Generics arrived but remain conservative. No union types, no pattern matching, no modifier algebra. The simplicity is a feature for many; a limitation for others.

Go — structural, no union types
// No union types: must use interface{}
func reduce[T, U any](s []T, f func(U,T)U, init U) U
// Any struct with matching fields satisfies interface
U
// Union type:
d Shape = Circle | Square | Triangle
// Generics on classes (v1); functions use interfaces:
d Reducible!
    f fold(acc: Reducible, item: Reducible) -> Reducible
05

Error handling

(T, error) return pairs are typed but silently ignorable — _ discards them with no warning by default. if err != nil is everywhere. No exhaustiveness, no policy arrays, no happy-path separation.

Go — if err != nil noise
data, err := fetch(url)
if err != nil { return err }
result, err := process(data)
if err != nil { return err }
U
f run(url: S) -> Data+A
    e.x([retry(3), log])
    data   = fetch(url)    // clean
    result = process(data) // clean
    r => result
06

Metaprogramming

No macros. Code generation via go generate runs external tools — separate from the compiler. Reflection at runtime. No compile-time execution. The deliberate simplicity means the language is easy to tool but limited in expressiveness.

Go — go generate: external tool
//go:generate stringer -type=Direction
// Runs external binary — not the compiler
// runtime reflection: reflect.TypeOf(x)
U
// c f __load: adds to_string to a type
c f __load()
    Direction.to_string = v => v.name
// compile-time, same language, no external tool
07

Injection & templates

Go's html/template package auto-escapes HTML — a good runtime defense. SQL injection requires parameterised queries by discipline. No compile-time enforcement of either.

Go — html auto-escapes, SQL does not
// html/template: runtime auto-escape
// SQL: discipline only
db.Query("SELECT * FROM u WHERE id=" + id)
// injection if id comes from user input
U
sql`SELECT * FROM u WHERE id = {id: I}`()
// I cannot carry string — structurally impossible
// Compile-time schema: column types checked
08

Ergonomics

Go's ergonomics are deliberately minimal — small keyword set, fast compile, simple toolchain. gofmt standardises formatting. The simplicity cuts both ways: approachable on day one, occasionally limiting later.

Go — WaitGroup + channel ceremony
var wg sync.WaitGroup
for _, item := range items {
    wg.Add(1)
    go func(i Item) {
        defer wg.Done()
        ch <- process(i)
    }(item)
}
wg.Wait(); close(ch)
U
results = items.map(i => a process(i))
// No WaitGroup. No channel. No defer.

U wins

  • Frame minimisation via +A
  • Null safety — compile-time
  • No GC pauses
  • Modifier algebra vs no modifiers
  • Typed error handling
  • Injection safety

Go wins

  • Proven at Google scale
  • Fastest compile times
  • Simplest toolchain
  • Channels as a primitive
  • Huge cloud/DevOps adoption
🍎

Swift

Modern, safe, expressive. Every Swift annotation is a workaround for a model not built around a unified algebra. U has the algebra.

Since2014 MemoryARC (automatic reference counting) TypingNominal, strong, inferred Asyncasync/await + actors (coloring) NullOptional<T> — enforced
01

Memory model

ARC — same mechanism as U, but U's is biased (non-atomic on the owning thread, atomic only on cross-thread access). Swift's ARC is always atomic for class reference counts, adding overhead on every retain/release even in single-threaded code. Swift structs are value types and avoid ARC; U's -R stack values are the equivalent.

Swift — ARC atomic even single-threaded
class Node { var next: Node? }  // ARC: always atomic
// Every retain/release: atomic op — even on one thread
struct Point { var x: Double }  // value type: no ARC
U
// -R: stack — zero ARC
pt: Point = Point(x: 1.0)
// +R: biased ARC — non-atomic on owning thread
cfg: Config+R = Config()
// atomic only when stolen across fibers
02

Null safety

Swift's Optional<T> is excellent and enforced. Syntax sugar (T?, ??, if let, guard let) makes it ergonomic. U's T+N is slightly more compact; both make non-null the type-safe path.

Swift — Optional ceremony
guard let user = getUser(id) else { return }
user.name  // unwrapped
// or: if let u = getUser(id) { ... }
U
user: User+N = get_user(id)
user ?? r => none  // must handle
user.name          // ✓ narrowed
03

Async & concurrency

Swift structured concurrency (async/await, actor, withTaskGroup) is well-designed but built from separate features. Each is a workaround for a problem the cactus stack dissolves: @escaping (closures that outlive scope), [weak self] (retain cycles), Sendable (values crossing actor boundaries), withTaskGroup (fan-out lifecycle). U's cactus handles all of these structurally.

04

Type system

Swift has protocols, generics, associated types, and existentials. some Protocol and any Protocol are distinct. Opaque types (some View) enable SwiftUI's DSL. No modifier algebra — mutability is a declaration-level keyword (var/let), not a composable modifier.

Swift — some vs any: two keywords
func make() -> some View { Text('hi') }  // opaque
var views: [any View] = [...]              // existential
// Distinct semantics, two keywords for same concept
U
d View!
    f draw(ctx: Context)   // interface — any impl

f make() -> View           // concrete — static dispatch
f make() -> View!          // interface — dynamic dispatch
// ! suffix = interface type. One axis, not two keywords.
05

Error handling

Swift's typed throws (SE-0413) finally arrived in Swift 6 — functions declare which error types they throw. Until then, throws was untyped. try at every call site. U's ! ErrorType in the signature with policy arrays achieves the same with zero happy-path noise.

Swift — try at every call site
// Swift 6: typed throws
func fetch() throws(NetworkError) -> Data
// try adds noise at every call site:
let d = try fetch()
let p = try parse(d)
U
f run() -> Data ! NetworkError
    e.x([retry(3), log])  // declare once
    data   = fetch()      // no try noise
    result = parse(data)
    r => result
06

Metaprogramming

Swift Macros (SE-0382) arrived in Swift 5.9 — compile-time code generation via typed AST transformation. More principled than Rust proc-macros. Still a separate system from the type system. U's c f is the same language in a different domain, not a separate AST manipulation API.

Swift Macros — external module, Syntax API
@attached(member)
macro AddID() = #externalMacro(
    module: 'MyMacros', type: 'AddIDMacro')
// Separate crate, Syntax library API
U
// c f __load: adds id field to User
c f __load()
    User.generate_id = () => UUID.new()
// same language — no external module
07

Injection & templates

No built-in SQL or HTML injection safety. String interpolation is typed but unguarded for injection contexts. Libraries provide safety by convention.

Swift — no injection safety
let q = "SELECT * FROM u WHERE id=\(id)"
db.execute(q)  // injection if id is tainted
// No compiler warning — compiles and ships
U
sql`SELECT * FROM u WHERE id = {id: I}`()
// I slot: parameterised at compile time
// Injection structurally impossible
08

Ergonomics

Swift's syntax is genuinely pleasant — trailing closures, property wrappers, result builders (SwiftUI). The accumulation of features (6 ways to handle optionals, 3 ways to declare protocols) adds cognitive load. U deliberately keeps one obvious way.

Swift — 4 concurrency annotations per task
func load(@escaping done: (Data)->Void) {}
btn.action = { [weak self] in self?.tap() }
Task { @MainActor in await refresh() }
struct Cfg: Sendable {}
U
f load(done: F(Data)->none) {}
btn.action = () => tap()
refresh()        // cactus handles isolation
// -R: Sendable by construction

U wins

  • No @escaping, [weak self], Sendable
  • No coloring — cactus handles it
  • Biased ARC — non-atomic single-thread
  • Injection safety built-in
  • One mechanism for all of async

Swift wins

  • Apple platform first-class
  • SwiftUI result builders
  • Mature iOS/macOS ecosystem
  • Swift Playgrounds for exploration
🟣

Kotlin

Java done right — null safety, coroutines, concise syntax. Closest in return-type annotation style (uses : T, which U moved away from).

Since2016 MemoryJVM GC TypingNominal, strong, inferred AsyncCoroutines (structured) NullT? — enforced
01

Memory model

JVM GC — well-tuned but pauses exist. No stack allocation as a concept. JVM startup cost. Native targets (Kotlin/Native) have their own memory model with ARC, but different semantics per target platform.

Kotlin — JVM heap for objects
val x = 42          // boxed Int on heap (JVM)
val list = listOf(1,2,3)  // ArrayList of boxed ints
// Kotlin/Native: ARC, different per target
U
x: I = 42           // 8 bytes, stack
list: [I] = [1,2,3]  // contiguous, stack, unboxed
x_heap: I+R = 42    // explicit heap opt-in
02

Null safety

Kotlin's nullable types (T?) are excellent and enforced. The Elvis operator (?:), safe call (?.), and !! for asserting non-null are clean. U's T+N and ?? are the same design with slightly different symbols.

Kotlin — platform types escape safety
// Java interop: platform type String! is nullable
val name: String = javaLib.getName()  // unsafe!
name!!.length  // KotlinNullPointerException
U
name: S = java_lib.get_name() ?? ""
// +N must be handled — no !! escape
// Compiler enforces at every use site
03

Async & concurrency

Kotlin coroutines with structured concurrency are the most principled async model before U. Scopes prevent goroutine leaks; launch/async manage fan-out. But suspend is a color: a suspend fun can only be called from another suspend fun or a coroutine builder. Making any function async requires rewriting every caller. Dispatchers (Dispatchers.IO vs Dispatchers.Main) must be explicitly chosen and threaded.

Kotlin — suspend infects callers
suspend fun foo(cfg: Config, n: Int): String { ... }

// bar must become suspend (infected)
suspend fun bar() {
    val cfg = loadConfig()  // saved in continuation
    val n   = 42
    val r = foo(cfg, n)     // suspend point
}
// caller needs coroutine builder + dispatcher
runBlocking(Dispatchers.IO) { bar() }
U — no infection, no dispatcher
a f foo(cfg: Config, n: I) -> S: ...

f bar()                // NOT suspend — not infected
    cfg = load_config()  // on bar's stack segment
    n   = 42
    r   = a foo(cfg, n)  // foo suspends → cactus branch
    // bar's segment alive — cfg, n reachable via ptr

f main()
    bar()   // plain call — works anywhere

The cactus stack. When a foo() suspends, U creates a new branch. The caller's stack segment stays alive — its local variables reachable via a parent pointer held by the child fiber. When the child completes, the parent resumes. No heap closure. No serialisation. No infection. Writing a foo() where foo is synchronous is a zero-cost no-op.

cactus_stack.u
// bar() is NOT marked async
f bar()
    cfg = load_config()        // on bar's stack segment
    n   = 42
    result = a foo(cfg, n)    // foo suspends:
    // ┌ bar segment alive ──────────┐  refcount = 1
    // │  cfg, n                     │
    // └─────────────────────────────┘
    // └── foo branch ← waiting, holds parent ptr
    // foo completes → refcount-- → bar resumes
    // cfg and n were NEVER copied or closed over
04

Type system

Kotlin has sealed classes, data classes, value classes, type aliases, extension functions. No modifier algebra — val/var is declaration-level. Generics with variance annotations (in/out) are powerful. Interop with Java means nullable escapes via platform types.

Kotlin — val/var declaration-level
val x = 42         // immutable binding only
var y = 42         // mutable — not in the type
// Concurrent mutation: requires external sync
U
x: I = 42           // -M: immutable (default)
y: I+M = 42         // +M: mutable — IN the type
state: Config+R+M(MVCC) = Config()
// mutation policy composable with type
05

Error handling

Kotlin inherited Java's unchecked exception model — anything can throw, nothing is declared. runCatching and Result<T> are library-level alternatives. No built-in typed error handling like U's ! ErrorType.

Kotlin — inherited unchecked exceptions
// No checked exceptions — anything can throw
try {
    val r = fetchData(url)
} catch (e: IOException) {
    retry()
}  // exhaustiveness? no.
U
f run(url: S) -> Data ! NetworkError
    e.x([retry(3), log])
    data = fetch(url)  // compiler: covered
    r => data
06

Metaprogramming

Kotlin Symbol Processing (KSP) for annotation processing. No compile-time execution in the language itself. Inline functions with reified type parameters preserve generics at runtime. @Serializable via KSP generates code externally.

Kotlin KSP — separate compilation phase
@Serializable
data class User(val name: String)
// KSP generates serialiser externally
// Separate phase, different API (ksymbol)
U
// c f: same language — no separate KSP phase
c f __load()
    User.to_json  = c build_serialiser(User, "json")
    User.from_json = c build_deserialiser(User, "json")
// integrated: same types, same error messages
07

Injection & templates

String templates ("Hello $name") are convenient but unguarded for injection. SQL injection requires discipline or ORMs. No compile-time injection safety.

Kotlin — string templates unguarded
"Hello $name"  // readable, but:
db.query("SELECT * FROM u WHERE id=$id")
// SQL injection: compiles, ships
U
sql`SELECT * FROM u WHERE id = {id: I}`()
// typed slot: never string-interpolated
// compile-time schema check included
08

Ergonomics

Kotlin's ergonomics are genuinely excellent — concise, readable, pragmatic. Extension functions and lambdas make DSLs natural. The JVM heritage means familiar tooling. U's : T for return types (old style) vs Kotlin's : T is the same; U switched to -> T to align with Python/Rust.

Kotlin — coroutine + dispatcher boilerplate
GlobalScope.launch(Dispatchers.IO) {
    val data = withContext(Dispatchers.Default) {
        fetchData()
    }
    process(data)
}
U
f run()
    data = a fetch_data()  // cactus handles
    process(data)         // no dispatcher

U wins

  • No GC — ARC + cactus
  • No coloring on coroutines
  • Typed error handling built-in
  • Injection safety
  • Compile-time metaprogramming

Kotlin wins

  • JVM ecosystem (Maven, Gradle)
  • Android first-class
  • Java interop — millions of libraries
  • Multiplatform (iOS, web, native)

Java

The enterprise standard. Verbose where U is terse; runtime where U is compile-time. The checked exception idea was right — the execution was wrong.

Since1995 MemoryJVM GC TypingNominal, verbose, improving AsyncVirtual threads (Project Loom) NullNullPointerException — runtime
01

Memory model

JVM GC is mature and well-tuned (G1, ZGC, Shenandoah) but pauses exist. Project Valhalla (value types) moves toward stack allocation for primitives — arriving decades after the need was clear. No ownership model.

Java — everything is heap
Result r = new Result();  // heap always
List list = new ArrayList<>();  // boxed
// Stack: primitives only. Project Valhalla: someday.
U
r: Result = Result()        // stack by default
r_heap: Result+R = Result() // explicit heap
list: [I] = [1,2,3]          // unboxed, contiguous
02

Null safety

NullPointerException is Java's most famous bug. Optional<T> was added in Java 8 but adoption is inconsistent. @Nullable and @NonNull annotations exist but are tool-dependent. No language-level enforcement.

Java — NPE in production
User user = getUser(id);  // null if missing
System.out.println(user.getName());  // NPE
// Optional: Java 8, inconsistent adoption
U
user: User+N = get_user(id)
user.name          // ✗ compile error
user ?? r => none  // handle
user.name          // ✓ safe
03

Async & concurrency

Project Loom (Java 21) finally brings goroutine-style ergonomics — virtual threads can block freely, no coloring, any method can block without annotation. The right answer, 15 years after Go. Virtual threads save the entire call stack per suspension — no frame minimisation equivalent to U's cactus branches. Earlier Java async approaches (CompletableFuture, reactive streams like RxJava, Spring WebFlux) introduced significant complexity and did have coloring.

Java — CompletableFuture (pre-Loom)
// Pre-Loom: coloring + callback hell
CompletableFuture.supplyAsync(() -> loadConfig())
    .thenApplyAsync(cfg ->
        foo(cfg, 42))           // infected chain
    .thenAcceptAsync(result ->
        store(result))          // buried happy path
    .exceptionally(e -> {
        log(e); return null;    // error interleaved
    });
U — cactus, clean happy path
f bar()
    cfg = load_config()      // on bar's segment
    result = a foo(cfg, 42)  // foo suspends:
    // bar's segment: alive via refcount
    // cfg: parent ptr from foo branch
    // NOT a full 2MB thread stack
    store(result)

The cactus stack. When a foo() suspends, U creates a new branch. The caller's stack segment stays alive — its locals reachable via a parent pointer held by the child fiber. When the child completes, the parent resumes. No heap closure. No serialisation. No infection. a foo() where foo is sync is a zero-cost no-op.

cactus_stack.u
f bar()
    cfg = load_config()       // on bar's stack segment
    n   = 42
    result = a foo(cfg, n)   // foo suspends → cactus branch
    // bar segment: alive, refcount=1
    // cfg, n: NOT copied — parent ptr from foo branch
    // foo completes → refcount-- → bar resumes
04

Type system

Java's type system is improving (records, sealed classes, pattern matching in switch) but carries 30 years of legacy. Generics are erased at runtime. No union types without sealed hierarchies. Verbosity is the historical complaint — Map<String, List<Integer>> on both sides of an assignment.

Java — verbose generics, erased
Map>> cache;
// Erased: no List.class at runtime
// instanceof check required for narrowing
U
{S: [User+N]}  // map of nullable-user lists
// Types not erased — full runtime info
// Pattern matching: native to type system
05

Error handling

Java's checked exceptions were the right idea — declared, typed, exhaustive. The execution failed: listing every subtype at every frame led to throws Exception everywhere. U inherits the right idea, fixes the execution: ! NetworkError covers all subtypes, policy arrays replace try/catch at every call site.

Java — checked exceptions done wrong
void fetch() throws NetworkException,
                  TimeoutException, SSLException {
    // add one → rewrite every caller
}
// Result: 'throws Exception' everywhere
U
f run(url: S) -> Data ! NetworkError
    e.x([retry(3), log])  // covers all subtypes
    data = fetch(url)
    r => data
06

Metaprogramming

Java annotation processing (APT) generates code from annotations. Spring and Lombok use it extensively. Reflection is powerful but runtime and slow. No compile-time execution in the language. Records and sealed classes reduce the need for boilerplate but don't eliminate it.

Java — APT + runtime reflection
@Getter @Setter    // Lombok: bytecode manipulation
@Entity            // Hibernate: runtime classpath scan
@Component         // Spring: runtime proxy
// All runtime — startup cost, no compile check
U
// c f: compile-time, same language
c f __load()
    User.get_name = () => t.name  // add getter
    User.get_age  = () => t.age   // add getter
    User.to_json  = c build_serialiser(User)
// zero startup cost — no classpath scan
07

Injection & templates

SQL injection has plagued Java applications for decades. PreparedStatement is the correct tool but discipline-dependent. No language-level enforcement. JEP 430 (String Templates, preview in Java 21) begins addressing this — inspired by the same insight as U's template tags.

Java — SQL injection 30 years on
String q = "SELECT * FROM u WHERE id=" + id;
stmt.execute(q);  // id from user → leak
// JEP 430 (Java 21 preview) finally addresses this
U
sql`SELECT * FROM u WHERE id = {id: I}`()
// Parameterised by construction — not concatenated
08

Ergonomics

Java has become significantly more ergonomic since Java 8 (lambdas, streams, var, records, switch expressions). Still verbose by modern standards. Boilerplate is the word most associated with Java. U's single-letter keywords (f, d, r) are an opposite extreme.

Java — verbose even in Java 21
public static void main(String[] args) {
    record User(String name, int age) {}
    sealed interface Shape permits Circle, Square {}
}
U
d User  { name: S; age: I }
d Shape = Circle | Square
f main()
    // no boilerplate

U wins

  • Stack allocation — no GC pauses
  • Null safety compile-time
  • Error handling — no try/catch noise
  • Injection safety
  • Compile-time metaprogramming
  • Minimal syntax

Java wins

  • Largest enterprise ecosystem
  • Maven Central — millions of libraries
  • Spring, Hibernate, Jakarta EE
  • 30 years of production hardening
  • Project Loom — virtual threads
⚙️

C++

The most expressive. Features accumulated, not compressed. Every U mechanism replaces a C++ mechanism that accreted over decades.

Since1985 MemoryManual + RAII + smart pointers TypingNominal, strong, complex Asyncstd::async / coroutines (C++20) Nullnullptr — undefined behaviour
01

Memory model

Manual memory with RAII destructors. unique_ptr, shared_ptr, weak_ptr add safety. shared_ptr is always atomic reference counting — no biased variant. Stack allocation is standard. The danger: use-after-free, double-free, buffer overflows are all undefined behaviour and compile fine. U's modifier algebra expresses the same ownership decisions with compile-time enforcement.

C++ — shared_ptr always atomic
auto s = std::make_shared();  // atomic ARC
auto s2 = s;   // atomic increment — even single-thread
// UB: use-after-free compiles without warning
U
cfg: Config+R = Config()   // biased ARC
cfg2 = cfg                 // non-atomic on owning thread
// atomic only when stolen across fibers
// No use-after-free possible
02

Null safety

nullptr can be assigned to any pointer. Dereferencing it is undefined behaviour — sometimes a crash, sometimes silent corruption. std::optional<T> (C++17) provides a safe nullable type but adoption is inconsistent. No language-level enforcement.

C++ — nullptr is undefined behaviour
Config* cfg = nullptr;
cfg->update();  // UB — compiler optimises assuming this
                 // doesn't happen → security hole
U
cfg: Config+N+R = load()
cfg ?? r => none    // must handle
cfg.update()        // ✓ safe — narrowed
03

Async & concurrency

C++20 coroutines are stackless and low-level — you implement the promise type yourself. std::async is higher-level but limited. No structured concurrency. std::thread for true parallelism. The primitives are powerful but composing them is manual.

04

Type system

C++ has the most expressive type system of any mainstream language — templates, concepts (C++20), constexpr, SFINAE, variadic templates. The power is real; the complexity is legendary. Thousand-line error messages from template failures. U's c f replaces C++ TMP with the same language in a different domain — typed, author-error-messages, no SFINAE.

C++ — template error: 300 lines
template>>
void process(T v);
// Wrong type → 300-line instantiation trace
// Author cannot control the error message
U
d Numeric!
    f scale(factor: N) -> Numeric
// Generic functions use interface polymorphism:
f process(v: Numeric) -> Numeric
    r => v.scale(2.0)
// wrong type → 'S does not implement Numeric'

SIMD, GPU, exact arithmetic, autodiff. C++ needs compiler-specific intrinsics (or #pragma omp simd) for vectorization, CUDA for GPU (a separate language and compilation pipeline), and external libraries like Eigen for linear algebra. There is no standard exact arithmetic type and no standard autodiff. In U the modifier system handles all three, and autodiff ships in the standard library.

C++ — intrinsics, CUDA, external libs
// SIMD: manual intrinsics or pragma
#include <immintrin.h>
__m256 a = _mm256_load_ps(data_a);  // AVX2 — x86 only
__m256 c = _mm256_add_ps(a, a);

// GPU: CUDA — separate .cu file, separate compiler
__global__ void add(float* a, float* c, int n) {
    int i = blockIdx.x*blockDim.x + threadIdx.x;
    if (i < n) c[i] = a[i] + a[i];
}
// cudaMalloc / cudaMemcpy / kernel<<<>>> / cudaFree...

// Exact arithmetic: no standard type
// __int128, boost::multiprecision, or GMP

// Autodiff: no standard — hand-roll or use library
U — modifiers, standard library
// +V: SIMD — compiler picks ISA, portable
a: [N32]+V = load_data()
c = a + a              // SIMD, ISA-agnostic

// +R(GPU): same expression, GPU dispatch
a_gpu: [N32]+R(GPU) = a.c(+R(GPU))  // copy to GPU
c_gpu = a_gpu + a_gpu  // GPU kernel — no .cu file

// 2D matrix on GPU: same API as CPU
w: [[N32]]+R(GPU) = load_weights()  // matrix, on GPU
out = w @ x_gpu        // matmul dispatched to GPU

// Q: exact rational — standard type
cost: Q = Q(355, 113)  // π approximation, exact
error = cost - Math.PI // exact symbolic difference

// Autodiff: standard library
loss = model(x) - target
Diff.grad(loss, model.weights)  // exact gradient
One syntax, three substrates. Switching from CPU scalar to SIMD to GPU is changing a modifier — the arithmetic expressions are identical. No separate compilation unit, no platform-specific intrinsics, no library import. CUDA programmers write add<<<blocks, threads>>>(a, c, n); U programmers write c = a + a on a +R(GPU) array. The modifier IS the target selection.
05

Error handling

C++ exceptions are untyped and have non-zero overhead even when not thrown (DWARF tables). noexcept helps but is advisory. Error codes as alternatives have no ergonomic composability. No declared error surface, no exhaustiveness.

C++ — untyped exceptions + DWARF overhead
void fetch(string url);  // can throw anything
// noexcept: advisory — not enforced
// DWARF tables: overhead even on happy path
U
f fetch(url: S) -> Data ! NetworkError
// declared, inherited, exhaustive
// zero overhead on non-error path
06

Metaprogramming

C++ template metaprogramming is Turing-complete but accidental — discovered via substitution failure rules, not designed. Concepts (C++20) improve the experience. consteval and constexpr add compile-time execution. Still a separate system from the runtime language. U's c f is the same language.

C++ TMP — discovered, not designed
// SFINAE: substitution failure, not an error
template>>
void f(T v);  // Concepts (C++20) improve, don't fix
U
// c f wraps any function at compile time:
c f double_it(func: F(N)->N) -> F(N)->N
    r => x => func(x) * 2.0
process_double = c double_it(process)
// author controls wrapping + error message
07

Injection & templates

No injection safety. String formatting is untyped (printf) or type-safe but cumbersome (std::format). SQL, HTML, shell injection are all discipline problems. No type system enforcement of any kind.

C++ — printf UB, SQL unguarded
printf("%s scored %d", score, name);
// args swapped: UB, security hole
cursor.exec("SELECT * FROM u WHERE id=" + id);
U
sql`SELECT * FROM u WHERE id = {id: I}`()
print("{score} scored {name: S}")
// Typed slots — wrong type = compile error
08

Ergonomics

C++ is infamous for its complexity — multiple ways to initialise, declare, cast, manage memory. Modern C++ (C++20, C++23) has improved significantly. The language accreted features rather than compressing them. U's design philosophy is the opposite: compress, don't accumulate.

C++ — multiple init syntaxes, hidden UB
int x{42};  int x=42;  int x(42);  auto x=42;
// 4 ways — different semantics in edge cases
// UB: signed overflow, strict aliasing, more
U
x: I = 42  // one way, always
// No UB by construction
// Modifiers declare intent explicitly

U wins

  • Memory safety by construction
  • Null safety — compile-time
  • Typed errors with exhaustiveness
  • Injection safety
  • c f vs C++ TMP — same language
  • Readable error messages
  • +V SIMD — ISA-portable, no intrinsics
  • +R(GPU) — no CUDA file, same expression
  • Q exact arithmetic — no Boost/GMP needed
  • Autodiff standard library

C++ wins

  • Largest systems codebase in history
  • Absolute performance ceiling
  • Unsafe escape when needed
  • Embedded / bare-metal
  • Zero-cost abstractions proven

Zig

Closest in philosophy — simple mechanisms, no hidden control flow. Zig makes allocation explicit; U encodes it in the modifier. Both reject magic.

Since2016 MemoryManual + explicit allocators TypingNominal, comptime-heavy AsyncAsync (coloring, stackless) NullOptional — enforced
01

Memory model

Zig makes allocation explicit via allocator parameters — every function that allocates takes an Allocator argument. U encodes the same information in the modifier: -R = stack (no allocator), +R = heap (runtime allocator). Same visibility, different mechanism. Zig's allocator chains propagate through call stacks; U's +R annotation propagates through types.

Zig
// Allocator chains everywhere
fn process(ally: Allocator, data: []T) ![]T {
    const result = try ally.alloc(T, data.len);
    defer ally.free(result);
    // allocator must reach every site
}
U
// Modifier encodes allocation intent
f process(data: [T]) -> [T]   // -R: stack
f process(data: [T]) -> [T]+R // +R: heap
// No allocator param to thread through
02

Null safety

Zig's ?T optional is enforced. orelse and if (opt) |val| handle absence. Similar to U's T+N and ??. Both make non-null values explicit at the type level.

Zig — ?T optional, orelse
const user: ?User = getUser(id);
const name = user.?.name;  // panics if null
// or: if (user) |u| u.name else "default"
U
user: User+N = get_user(id)
user ?? r => none   // must handle
user.name           // ✓ safe
03

Async

Zig's async system has been under significant redesign — the original stackless coroutine model was deprecated in 2023. Coloring applies: async fn returns a frame type distinct from regular functions. No structured concurrency. No cactus stack equivalent — parent frames are not retained when a child fiber suspends; captured variables must be explicitly managed. This means U's cactus-based "plain function calling async function" ergonomic is not achievable in Zig's model.

Zig — coloring, manual frame
// async fn has different type than fn
fn foo(cfg: *Config, n: u32) void { ... }
// Zig async: different story
// Frame must be kept alive by caller
// No cactus — parent not retained automatically
U — cactus, no annotation
a f foo(cfg: Config, n: I) -> S: ...

f bar()                    // NOT async
    cfg = load_config()
    n   = 42
    result = a foo(cfg, n)
    // foo suspends → cactus branch
    // bar's segment alive automatically
    // cfg, n: parent pointer — not copied
04

Type system

Zig's comptime is powerful — types are first-class values, generic functions are just functions with comptime parameters. No trait system or algebraic types. Error unions (!T) are built-in. The simplicity is genuine — one consistent model for compile-time and runtime computation.

Zig comptime — types as values (elegant)
fn Vec(comptime T: type, n: comptime_int) type {
    return struct { data: [n]T };
}
const V3f32 = Vec(f32, 3);  // works!
U
// Generic class — ElemType: multi-letter rule
d Vec[ElemType]
    data: [ElemType]

// Fixed-size: -M(n) on the array, not a type parameter
d Vec3f32
    data: [N32]-M(3)    // 3-element, stack, compile-time fixed

V3 = Vec3f32(data: [1.0, 2.0, 3.0])
05

Error handling

Zig's error unions (!T or MyError!T) with try for propagation are clean and typed. Errors are inferred from the function body — no declaration required. U requires declaring ! ErrorType in the signature; Zig infers it. Both achieve typed exhaustive error handling.

Zig — error union, inferred set
fn fetch(url: []const u8) !Data {
    const conn = try connect(url);
    return try conn.read();  // set inferred
}
U
f fetch(url: S) -> Data ! NetworkError
// declared explicitly: contract is clear
e.x([retry(3)])
data = connect(url).read()
06

Metaprogramming

Zig's comptime is the closest existing analogue to U's c f — same language at compile time, types as values, compile-time execution is normal code. The key difference: Zig's comptime has no capability system, no modifier algebra, no template tag hooks. It is a general-purpose compile-time computation mechanism without the adapter pattern.

Zig comptime — powerful, no capability system
fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}
// No capability gating. No template tag hooks.
U
// Generic function [T] — same as make_getter[T] pattern:
f max[T](a: T, b: T) -> T
    r => a > b ? a : b

// c f adds capability system on top:
// __check, __validate hooks for template tags
// adapters — not part of Zig comptime
07

Injection & templates

No injection safety. String formatting is manual. SQL injection requires discipline. No template tag system.

Zig — no injection safety
const q = try std.fmt.allocPrint(
    ally,
    "SELECT * FROM u WHERE id={d}", .{id}
);
// No compile-time schema check
U
sql`SELECT * FROM u WHERE id = {id: I}`()
// __schema() at compile time: columns checked
// Type mismatch = build error
08

Ergonomics

Zig's philosophy — no hidden control flow, explicit everything — is shared with U. Both are opinionated about what the language won't do for you. Zig is more minimal in syntax; U has more built-in (modifier algebra, template tags, cactus stack). Both reject magic.

Zig — explicit, minimal, no magic
// No hidden control flow — by design
defer ally.free(buf);  // explicit cleanup
// comptime: same language — elegant
// No OOP, no exceptions, no GC, no async magic
U
// Same philosophy: explicit, no hidden flow
// c f: same language — same elegance
// -R -M -N: explicit defaults, zero annotation
// Cactus: explicit model, no hidden GC/async

U wins

  • No coloring — cactus handles async
  • Modifier algebra vs allocator params
  • Built-in injection safety
  • Capability system for c f
  • Template tags as DSL engine

Zig wins

  • C interop — direct, no FFI
  • Smallest binary size
  • Embedded / bare-metal focus
  • comptime — simpler model
  • Bun is written in Zig
🔷

C#

Microsoft's answer to Java — more expressive, more modern. LINQ shows the right instinct for typed DSLs. U generalises it.

Since2000 Memory.NET GC TypingNominal, strong, improving Asyncasync/await (coloring) NullNullable reference types (opt-in)
01

Memory model

.NET GC with Span<T>, ref structs, and stackalloc for stack allocation. C# 7+ made stack allocation ergonomic for hot paths. No ownership model — refs can escape freely. Span<T> enforces stack confinement at the type level — the closest C# comes to -R.

C# — GC, Span for stack opt-in
var r = new Result();       // heap, GC
// Span: stack-only (C# 7.2+, opt-in)
Span s = stackalloc int[8];
// Span enforced not to escape by type system
U
r: Result = Result()        // stack (default, -R)
r_heap: Result+R = Result() // explicit heap
// Modifier is the declaration — no stackalloc
02

Null safety

Nullable reference types (C# 8+) add string? vs string — opt-in per project. Non-null is the default with the pragma enabled. Better than Java, opt-in like TypeScript's strict mode. U's -N default is always-on.

C# — nullable opt-in, ! bypass
// Requires #nullable enable
string? name = GetName();  // nullable
name!.Length  // ✓ compiles — unsafe bypass
// Can always suppress the check
U
name: S+N = get_name()
name.length    // ✗ compile error
name ?? r => none  // must handle
name.length    // ✓ safe — no ! bypass
03

Async — original and coloring

C# invented async/await in 2012 — every other language copied it, coloring problem included. async infects every caller. ConfigureAwait(false) is required in library code to avoid deadlocks on synchronisation contexts — forgetting it is a famous cause of production ASP.NET deadlocks. Task vs ValueTask tradeoffs add further cognitive load.

C# — coloring + ConfigureAwait
async Task<string> Foo(Config cfg, int n) { ... }

// bar must become async (infected)
async Task Bar() {
    var cfg = LoadConfig();  // must survive await
    var n   = 42;
    // cfg, n serialised into heap Task state machine
    var r = await Foo(cfg, n).ConfigureAwait(false);
    // ↑ forget this in library code → deadlock
}
// main must be async (infected)
async Task Main() { await Bar(); }
U — no infection, no ConfigureAwait
a f foo(cfg: Config, n: I) -> S: ...

f bar()                  // NOT async
    cfg = load_config()  // on bar's stack segment
    n   = 42
    r   = a foo(cfg, n)  // foo suspends → cactus branch
    // bar's segment: alive — cfg, n reachable
    // No ConfigureAwait. No deadlock scenario.

f main()
    bar()  // plain — not infected

The cactus stack. When a foo() suspends, U creates a new branch. The caller's stack segment stays alive — its locals reachable via a parent pointer held by the child fiber. When the child completes, the parent resumes. No heap closure. No serialisation. No infection. a foo() where foo is sync is a zero-cost no-op.

cactus_stack.u
f bar()
    cfg = load_config()       // on bar's stack segment
    n   = 42
    result = a foo(cfg, n)   // foo suspends → cactus branch
    // bar segment: alive, refcount=1
    // cfg, n: NOT copied — parent ptr from foo branch
    // foo completes → refcount-- → bar resumes
04

Type system

C# has records, pattern matching, discriminated unions (via OneOf library), generics, extension methods. LINQ demonstrates typed query composition — the same insight as U's sql\`\` but as a library, not a language feature. No modifier algebra.

C# — readonly is shallow
readonly record struct Pt(double X, double Y);
// readonly: can't reassign fields
// But: nested mutable objects still mutable
// No ownership, no mutation policy
U
pt: Point = Point(x:1.0, y:2.0)  // -M: immutable
pt_m: Point+M = Point(x:1.0, y:2.0) // +M: mutable
// Mutation policy is a composable modifier
05

Error handling

Untyped exceptions inherited from .NET. Result<T, E> patterns via libraries. No language-level typed error surfaces. The try/catch ceremony is everywhere in production C# code.

C# — untyped exceptions inherited from .NET
try {
    var data = await FetchAsync(url);
    Process(data);
} catch (HttpRequestException e) {
    Retry();
} catch (Exception) { throw; }
U
a f run(url: S) -> none
    e.x([retry(3), log])
    data = a fetch(url)  // happy path
    process(data)
06

Metaprogramming

C# Source Generators (Roslyn) are powerful — typed AST transformation at compile time. More principled than Java APT. Roslyn's API is rich. Still a separate system from the type system. C# attributes are runtime by default.

C# Source Generators — Roslyn API
[Generator]
public class MyGen : IIncrementalGenerator {
    public void Initialize(
        IncrementalGeneratorInitializationContext ctx) {...}
}  // separate project, Roslyn syntax API
U
// c f: same language — no separate Roslyn project
c f __load()
    User.to_json  = c build_serialiser(User, "json")
    User.from_json = c build_deserialiser(User, "json")
    // full User type available — not a token stream
07

Injection & templates

LINQ to SQL was the right instinct — typed queries that compile against a schema. Entity Framework extends it. Interpolated strings ($"...") are type-safe for formatting but not for injection contexts. No compile-time injection safety equivalent to U's template tags.

C# — LINQ typed, SQL still discipline
// LINQ: typed ✓
var names=users.Where(u=>u.Age>18).Select(u=>u.Name);
// Raw SQL: still user discipline:
db.Execute($"SELECT * FROM u WHERE id={id}");
U
// Template tags: typed for every target
sql`SELECT name FROM u WHERE age > {min: I}`()
graphql`query { users(minAge:{min:I}) { name } }`
08

Ergonomics

C# has improved dramatically — records, top-level statements, pattern matching, primary constructors. LINQ and extension methods make it expressive. The ecosystem (NuGet, .NET) is excellent. Still carries C-style ceremony in places where U uses single-letter keywords.

C# — ConfigureAwait deadlock trap
async Task FetchAsync(string url){...}
async Task RunAsync() {
    // Forget ConfigureAwait(false) in lib → deadlock
    var r=await FetchAsync(url).ConfigureAwait(false);
}
U
a f fetch(url: S) -> S   // async — no keyword infection
f run()
    result = a fetch(url)  // no ConfigureAwait ever
    // one scheduler — deadlock impossible

U wins

  • No GC pauses — ARC + cactus
  • No coloring
  • Null safety always-on
  • Typed errors — no exceptions
  • Template tags > LINQ for injection
  • Compile-time same language as runtime

C# wins

  • .NET ecosystem — NuGet
  • Azure / Microsoft integration
  • Unity game development
  • Roslyn tooling — best-in-class
  • WPF / WinUI / MAUI