U Language Reference GuideCompanion to POPL 2027 · Anonymous submission
Overview

Why U?

Most languages aim for one pit of success. U aims for two — one for developers, one for every tool that reasons about the code afterward. To our knowledge, U is the first language designed for two audiences at once: humans who write and review code, and LLMs that autonomously write, analyse, and reason about large codebases.

Pit 1 — For developers: the easy path is the safe path

A function written in U with no annotations is already correct by default. Parameters are local, immutable, and non-null. The compiled output uses no heap allocation, no ARC operations, no locks, no null checks. You get the safest and fastest possible program without writing a single modifier.

Every cost you might want to add — heap allocation, mutability, shared ownership, async suspension, nullable values — requires an explicit + annotation. You cannot accidentally pay for something you didn't ask for. You cannot accidentally make something unsafe without a visible opt-in. The pit of success means the path of least resistance is also the path of least risk.

Uzero annotations = maximum safety
// No annotations — you still get all of this for free:
f process(data: [N], label: S): Stats
    total = data.reduce((acc, val) => acc + val, 0.0)
    r => { total, mean: total / data.length, label }

// ✅ No heap allocation       (-R default)
// ✅ No null dereference       (-N default)
// ✅ No data race              (-R: nothing shared)
// ✅ No ARC cost               (-R: no refcount)
// ✅ Zero-copy return          (r.field = DPS)
// ✅ Bounds-safe iteration     (.x() / .reduce())

Pit 2 — For tooling: declared structure beats inferred structure

Today when a compiler, static analyser, or LLM inspects a codebase, it performs expensive reconstruction work: inferring ownership patterns, guessing threading assumptions, rebuilding the call graph, reconstructing what each binding is supposed to mean. That work is paid on every query, produces approximations, and is discarded at session end.

In a U codebase, every binding site is a machine-readable declaration of intent. +R+M(MVCC) says exactly how a shared object behaves under concurrent writes. +N marks every nullable boundary. ! NetworkError declares exactly what can fail. The entire semantic structure of the program is explicit in the syntax — not buried in conventions, comments, or commit history.

Existing language — inference every time

// Tool must infer:
// — is config shared or local?
// — can it be null?
// — what write policy applies?
// — what errors can this throw?
// — does this escape its scope?
let config = loadConfig();
config.host = newHost;
// Expensive. Approximate. Repeated.

U — structure declared once, read forever

// Tool reads directly:
// — shared heap, MVCC writes
// — non-null
// — MVCC policy
// — declares ! NetworkError
// — +R = heap, will not escape as -R
config: Config+R+M(MVCC) = load()
config << { host: newHost }
// O(1). Exact. Stable across queries.

This changes the economics of code analysis. An entire class of infrastructure bugs — data races, use-after-free, null dereference, async violations, injection attacks — is structurally impossible and doesn't need to be searched for at all. The bugs that remain are logic bugs, and they can be targeted precisely because the program's semantic structure is already declared.

The compounding effect. As codebases grow, the cost of inference scales with size and degrades as conventions drift. The cost of reading a type annotation is constant. A million-line U codebase is structurally transparent in a way a million-line TypeScript or C++ codebase isn't — every ownership boundary, every async crossing, every nullable value, every write policy is visible in the source, queryable in O(1), and verified correct by the compiler.
Designed for two audiences simultaneously

Every language before U was designed for one audience: the human developer. LLMs arrived later and had to reconstruct implicit structure — ownership patterns, threading assumptions, nullable boundaries — through expensive inference, on every query, discarded at session end.

In U, that structure is declared at the binding site, verified by the compiler, and readable by any tool in O(1). The same annotations that make code correct for humans make it legible for LLMs. To our knowledge, U is the first language that treats LLMs as a first-class consumer rather than an afterthought.

Fully typed. No escape hatches.

Every binding in U has a statically known type at compile time. There is no any, no root Object, no untyped back door. The : type test is for narrowing only — checking whether a Shape is a Circle, whether an object implements Drawable. It is never needed to ask "is this typed?" because every value always is.

Java has Object casts. TypeScript has any. Go has interface{}. Swift has Any. U has none of these. If you cannot name the type of a value, the design is incomplete.

Guide

Contents

00Why U?Two pits of success — for developers and for tooling 01CommentsLike C · /* */ nesting supported 02Primitives & LiteralsI N S L B V — single-letter types, width suffixes 03.5ParenthesesNo {} · () for blocks and grouping · last expr is value 03.6ComprehensionsShorthand {host, port} · spread {...obj, key: val} 03VariablesInferred types · identifiers must be multi-letter 04Functions & Lambdasf named · => lambda · r return · zero-copy DPS 04.5Closures & Arrowsf hoisted + named · () => anonymous + captures t · modifier rules apply 05KeywordsAll 10, single letter — complete and settled 06Modifiers7 commutative dimensions · zero annotation = safest & fastest 05.5Namespaces & Moduleso keyword · import · export · t inference · multi-file extension 06.5+F Function ModifierT +F = any callable producing T · bare form most general 07Classes & Selfd · : inheritance · +P policy · no multiple inheritance 07.5Type Hierarchiesd Foo.Bar : Foo · enum replacement · typed dispatch · +G static 07.6Interfacesd Foo! · f method()! · never static · untyped params banned 08MapsDynamic keys · full modifier enforcement on values 08.5Magic Methods__hash · __equals · __serialize · __unserialize · __merge 09Null Safety-N default — null dereference is a compile error 10Conditionals?! : & | ?? — no if/else/switch/match keywords 11Iteration.x() replaces all loops · always bounded · pipelines 11.5Ranges1..5 · * step · scalar lift · array slicing · zero allocation 12Generatorsw loop · y yield · w+I intentionally infinite 13Error Handlinge throw · e.x() typed handler chain · object interfaces · ! ErrType 14Fibers & Red-BlueStackful · any depth can suspend · async coloring solved 15Async — a keywordAuto-await default · a f() opt-in · T +A pending type 15.5+A as Pending Typea and +A co-required · exactly two valid call forms 15.6Zero-Cost PrincipleLazy materialization · no cost unless both conditions hold 15.7BackpressureT +A return in .x() → bounded concurrency, no primitives 16Scheduler & Yielda; cooperative · a n timed · toposort · typed pub/sub 17Proxy Policies+R(RAII) / +M(Actor|MVCC|CRDT|Mutex) · class-level or binding-site 18Templates & i18nhtml/sql/msg interpolation escaped at compile time · injection = type error 19Arithmetic SafetySafe by default · ! to opt out · compiler elides proven checks 20Memory ModelTiered from type · ARC without atomics · no GC pauses 21Copy — .c()Only explicit copy mechanism · lazy COW · free when refcount=1 22Zero-Copy ReturnsDestination-passing style · compiler-guaranteed, not hoped for 23Defaults-R -M -N: zero annotation = max safety and max speed simultaneously 24Safety by Construction11 error categories — type errors, not runtime crashes 17.5Access Policies +P()RAII · ARC · MVCC · CRDT · Actor · vs GC languages 17.6MVCC DefaultDefault write policy · atomicity · rollback on exception 23.5vs C++, Java, Rust, GoExceptions · RAII · null safety — detailed comparison 25vs Go / JS / SwiftColoring solved · no microtask starvation · minimal fiber frames 26vs Rust / ZigMultiple writers via proxy · modifier replaces borrow checker + allocator param 27Formal Guarantees22 theorems · 8 corollaries · all proved in the λ_U calculus

Seven modifier dimensions replace escape analysis, alias analysis, null checks, ARC atomics, async coloring, injection vulnerabilities, and i18n errors. Each annotation is a proved safety invariant and a compiler oracle. The zero-annotation default is the safest path and the fastest path.

Overview

U — Where the Easy Path Is the Safe Path

U is a systems programming language built around one insight: type annotations serve two clients simultaneously. The programmer gets safety guarantees. The compiler gets optimization licenses. Same annotation. Both benefits.

Default costs: The zero-annotation default -R -M -N is the safest path and the fastest path. Every cost — heap allocation, mutability, nullability, sharing, async survival — is opt-in with an explicit +. You cannot accidentally pay for something you didn't ask for.
Uhello.u
// No annotations — local, immutable, non-null, zero cost
f main()
    greeting = "Hello, world!"    // S -R -M -N — inferred
    console.log(greeting)

// greeting is:  local (no heap),  immutable (no lock),  non-null (no check)
// The compiler emits zero allocation, zero null check, zero ARC operation.
01Syntax

Comments — like C, but /* */ supports nesting

Single-line // and block /* */, identical to C. The one difference: block comments nest. This means you can wrap any section of code in /* ... */ without first checking whether it already contains block comments.

Why nesting matters: In C, commenting out a block that contains /* already commented */ ends the outer comment early, leaving the rest as live code. U's nested /* */ means comment-out always works, regardless of what's inside.
C / C++ /* f() /* deprecated */ g() ← LIVE! outer ended */ inner */ ends outer comment g() compiles as live code U /* ← depth 1 f() /* deprecated */ ← depth 2, then 1 g() ← still commented out */ ← depth 0, done inner */ just decrements depth g() stays inside the comment
Ucomments.u
// Single-line — to end of line

/* Block — spans lines */

/* Nesting works — unlike C
   /* inner comment */
   still commented out here
*/

/* Comment out any block safely, even if it has block comments inside:
    f oldVersion(data: [I])  /* deprecated */
        process(data)
*/

f add(a: I, b: I): I  // inline is fine too
    r => a + b
02Types

Primitives & Literals

All primitive types are single uppercase letters. Width specifiers suffix the letter. No verbosity — I not int32_t.

TypeMeaningWidth variantsLiterals
ISigned integerI8 I16 I32 I6442  -7  0xff
UUnsigned integerU8 U16 U32 U64255u  0b1010u
NFloat (number)N32 N643.14  1.0e-9
DExact decimalD32 D64 D1289.99d  0.001d
SString (UTF-8)"hello"
LBooleantrue  false
BByte (raw)0x000xFF
noneNull literaltype T+N
trueBoolean truetype L
falseBoolean falsetype L
Uliterals.u
count  = 42          // I  inferred
small: I32 = -7     // I32 explicit width
ratio  = 3.14        // N  float64
price  = 9.99d       // D  exact decimal, no float rounding
msg    = "Hello!"    // S  UTF-8 string
alive  = true        // L  boolean
nothing: I+N = none // nullable integer — requires +N annotation

// Ranges — produce iterable values
exclusive = 0..10     // [0,10) — 0,1,2,...,9
inclusive = 1..=5    // [1,5]  — 1,2,3,4,5

RegExp — class, not literal syntax

Regular expressions use RegExp(pattern, flags) — a plain class constructor. No /pattern/flags syntax. The compiler optimizes calls with string literals to compiled patterns at build time.

Uregexp.u
email = RegExp("[a-z]+@[a-z]+\.[a-z]+", "i")  // compiled at build time
match = email.match(input)                        // returns [S]+N
clean = RegExp("\s+").replace(text, " ")
03.6Syntax

Comprehensions & Spread

Shorthand literal syntax for constructing objects and maps from existing values. Works for class instantiation and map literals.

Ucomprehensions.u
// Shorthand: variable name used as key
host = "localhost"
port = 8080
config = Config({ host, port })        // Config({ host: host, port: port })

// Spread + override: ... spreads all fields, then overrides
newConfig = Config({ ...config, host: "newhost" })
newMap    = { ...meta, "version": 4 }                // map spread

// Works in any expression context
servers = configs.map((cfg) => Server({ ...cfg, active: true }))
03Types

Variables & Type Inference

U infers types from the right-hand side. The inferred type gets default modifiers -R -M -N. Annotations are optional — add them only when you want to change the defaults.

Identifiers must be multi-letter. Single letters are reserved for keywords. Enforced by the compiler. You write count not c, index not i. Descriptive names are structural.
Uvariables.u
// Inferred — all default to -R -M -N
count    = 0              // I  -R +M -N  (local mutable, class default)
ratio    = 3.14           // N  -R -M -N
greeting = "hello"       // S  -R -M -N
point    = { x: 1.0, y: 2.0 }  // {x:N,y:N} -R +M -N
items    = [1, 2, 3]      // [I] -R +M -N

// Explicit — override inferred type or modifiers
shared: I+R+M   = 0     // shared mutable integer on heap
config: Config+R-M = load() // shared immutable Config
maybe:  User+N    = none // nullable User
width:  I32        = 640  // explicit 32-bit width
03.5Syntax

Parentheses — Grouping and Block Scope

U has no {} curly braces. () are the only grouping construct. Inside parentheses, newlines are free — the expression continues until the closing ). The last expression inside a block is its value. This applies everywhere: argument lists, conditions, chained operations, multi-statement blocks.

No curly braces anywhere in U. Function and class bodies use indentation. Inline multi-line expressions use (). Everything is an expression, so a () block produces a value.
Uparens.u
// Grouping across newlines — continues until )
total = (
    subtotal
    + tax
    + shipping
)

// Block expression — multiple statements, last is value
result = (
    step1 = doFirstThing()
    step2 = doSecondThing(step1)
    step1 + step2       // ← value of the block
)

// Complex condition spread across lines
valid = (
    user.age >= 18 &
    user.verified &
    !user.banned
)

// Argument list — newlines freely between args
response = submitOrder(
    orderId,
    items.x((i) => i.c(+R)),
    shipping.address,
    payment.token
)

// Pipeline of operations that would be awkward inline
processed = (
    raw  = a fetch(url)
    clean = sanitize(raw)
    valid = validate(clean)
    valid               // ← returned value
)

// Chained method calls broken across lines
summary = (
    orders
        .x((order) => order.paid)
        .x((order) => order.amount)
        .reduce((acc, amt) => acc + amt, 0.0)
)

// Ternary branches using () for multi-line without indentation rules
label = count > 0
? (
    plural = count > 1
    plural ? "{count} items" ! "1 item"
)
! "empty"
04Functions

Functions & Lambdas

Named functions use f. Lambdas use => alone — no f needed. Return is assignment to r. Building the return value field-by-field with r.field = ... writes directly into the caller's pre-allocated stack slot — no copy ever happens.

Ufunctions.u
// Named function
f add(left: I, right: I): I
    r => left + right

// Short form — expression body
f square(val: N) = val * val

// Lambda — => implies anonymous, no f needed
double    = val => val * 2
increment = val => val + 1

// Multi-expression lambda — last expression is the return value
clean = item => (
    trimmed = item.trim()
    trimmed.lowercase()   // returned
)

// Return-by-construction — r.field writes into caller's pre-allocated slot
// No copy on return. Destination-passing style, guaranteed by the type system.
f buildUser(name: S, age: I): User
    r.name = name    // writes directly to caller's frame
    r.age  = age     // no copy occurs at return

// Default parameters
f connect(host: S, port: I = 8080): Connection+R+M+N
    r => tcp.dial(host, port)

// Higher-order — F(I):S means "function taking I, returning S"
f applyTwice(fn: F(I):I, val: I): I
    r => fn(fn(val))

result = applyTwice(double, 3)  // 12

Optional named parameters — {} at end

The last parameter may be an anonymous typed map, destructured by the compiler. Named params are optional, have declared types and defaults, and never break existing callers when extended.

Rules:
• Positional params: required, ordered, no defaults — always honest
• One {} at the end: anonymous destructured map, defaults allowed
• No defaults on positional params — ever (compiler error)
• No anonymous {} in middle positions — use a named class there
Unamed-params.u
// {} at end — optional named params with defaults
f fetch(url: S, count: I, { timeout: I = 30, verbose: L = false })

fetch("api.com", 5)                           // all defaults
fetch("api.com", 5, { verbose: true })         // one override
fetch("api.com", 5, { timeout: 60, verbose: true }) // two overrides

// Adding new optional never breaks callers:
// f fetch(url: S, count: I, { timeout: I = 30, verbose: L = false, retries: I = 3 })
// All existing call sites still work unchanged

// {} in middle? Use a named class instead:
d FetchOpts { timeout: I = 30 }
f weirdFunc(x: I, opts: FetchOpts, y: S)     // explicit, named
weirdFunc(5, FetchOpts({ timeout: 60 }), "hi")

// ❌ No defaults on positional params — compile error:
// f bad(x: I, y: I = 5)  ← banned

Instantiation — ClassName({...})

No new keyword. ClassName({...}) creates an instance — familiar to JS developers. If no __construct is defined, the compiler auto-generates one from the declared fields.

Uinstantiation.u
d Config
    host: S  = "localhost"
    port: I  = 8080
    debug: L = false

cfg  = Config({})                           // all defaults
prod = Config({ host: "prod.api.com", port: 443 })

// __construct overrides auto-generated init:
d Cache
    maxSize: I
    items: {S: S}+M = {}  // typed map

    f __construct({ maxSize: I = 100 })
        validate(t.maxSize > 0)             // custom logic
        // (Cache has no parent — parent calls only apply when extending a class)

Surplus-argument dropping

A handler may declare fewer parameters than it will receive. Surplus arguments are silently dropped from the right. This is not the same as default values — the caller supplies all arguments; the callee simply ignores the extras.

Rule: if a function declares n parameters and is called with m > n arguments, the first n arguments are bound and the rest discarded. The declared parameter types must match the first n types the caller supplies — no implicit coercion.

Primary use case: iteration handlers. Array and map methods pass (value, key); handlers that only care about the value declare one parameter and the key is dropped.
Uarity.u
// Array iteration: (value, index) passed, handler takes only value
nums = [10, 20, 30]
nums.x((n: I) => print(n.toString()))     // index silently dropped
nums.x((n: I, i: I) => print(i.toString() + ": " + n.toString()))  // both

// Map iteration: (value, key) passed
scores: {S: N} = { "alice": 9.8, "bob": 7.4 }
scores.x((score: N) => process(score))         // key silently dropped
scores.x((score: N, name: S) => print(name))  // both — note value comes first

// Named functions also obey the rule when used as handlers:
f process(item: S): L   r => item.length > 0
items.filter(process)   // (S, I) passed, process takes (S) — I dropped

// Compile error — declared param type must match what's supplied:
// scores.x((score: I) => ...)  ← ✗ N supplied, I declared
04.5Functions

Closures & Arrow Functions

U has two function forms. They differ on naming, hoisting, and t capture.

f name(params)(params) => expr
NameRequiredOptional (usually anonymous)
HoistedYes — callable before declarationNo — it's a value expression
t (self)Explicit parameter — not capturedCaptured from enclosing scope
Multi-lineIndented body(params) => ( ... block ... )
Use forReusable named logic, methodsCallbacks, policies, inline transforms
No anonymous f functions. If you write f, name it. Names make functions findable, reusable, and documentable. Arrow functions are intentionally disposable — use them for one-shot inline logic.
-> for types, => for values — one symbol per role.
(I)->I — a function type: "takes I, returns I". Appears in type annotations after :.
(x: I) => x * 2 — a function value: "given x of type I, produce x * 2". Appears in expressions.

The visual distinction is deliberate. -> is the classical type-theory arrow (Haskell, Swift, Kotlin). => produces a value. Seeing (NetworkError)->() you immediately know it's a type; seeing (err) => retry(err) you know it's a closure.
Uclosures.u
// Named f — hoisted, t is explicit, not captured
f greet(name: S): S
    r => "Hello, " + name

greet("Alice")   // fine even before the f declaration above (hoisted)

// Arrow function — anonymous, captures t, is a value
greet: (S)->S = (name) => "Hello, " + name

// Multi-line arrow uses () block — last expr is the value
process: (I)->S = (num) => (
    doubled = num * 2
    "result: {doubled}"
)

// Closure — captures outer variable, subject to modifier rules
f makeMultiplier(factor: I): (I)->I
    r => (num: I) => num * factor    // captures factor (I is -R, value-copied in)

double = makeMultiplier(2)    // double: (I)->I
double(5)                     // 10

// Capture rules — same modifier rules as any variable
f setup()
    local: S = "hello"                // -R stack
    msg: S+R = local.c(+R)          // heap copy

    ok: (()->())+F-R = () => log(local)    // ✅ non-escaping, in-scope only
    bad: (()->())+F+R = () => log(local)   // ❌ ERROR: +F +R captures -R local
    fine: (()->())+F+R = () => log(msg)    // ✅ +R captured into +F +R

// In a class: arrow captures t, f() does not
d Button
    clicks: I+M = 0

    f onClick()                    // t is explicit — this is a method call
        t.clicks = t.clicks + 1

    handler: (ClickEvent)->() = (e) =>  // arrow captures t from Button instance
        t.clicks = t.clicks + 1        // ✅ t is the Button

// Function values carry all modifiers like any other value
policy: (NetworkError)->()+R = (err) => retry(err)   // heap, shareable
policies: [(NetworkError)->()] = [logToSentry, retryOnRecoverable]  // array

// Passed to e.x() as a policy array — order defined, preserved
e.x(policies)
05Language

Keywords — 11 + 3

Fourteen keywords total: eleven single-letter (a b c d e f o r t w y) and three multi-letter literals (none, true, false). You can memorize the entire language vocabulary in five minutes.

a
async / yield
Three roles: (1) a f declaration = behavioral keyword marking function as suspendable. (2) a f() call site = opt into T +A pending (co-requires +A on LHS). (3) a; or a n = cooperative/timed yield.
b
break
Exit a w generator/loop early.
c
continue
Skip to next iteration inside a w loop.
d
define class
Class definition. No separate struct — modifiers determine layout and cost.
e
error / throw
Throw an error that propagates up the fiber stack automatically.
f
function
Named function declaration. Lambdas use => without f.
r
return target
r => value or r.field = ... for zero-copy return-by-construction.
t
this / self
Self reference inside a method. Like this in JS or self in Python.
w
generator / loop
Loop construct. Warns if no b reachable. w+I for intentionally infinite.
y
yield
Yield a value from a w generator to the consumer.
o
outside / namespace
Import or export a namespace. o Math.Linear imports, o Math.Linear => (...) exports. Four import forms: plain, .* glob, scoped (...), scoped glob .*(...).
none
null literal
The null value. Type of a nullable binding is T+N. Use ?? to coalesce and +N to permit.
true
boolean true
The boolean true value. Type L.
false
boolean false
The boolean false value. Type L.
Ukeywords_demo.u
// f — named function          d — class definition
d Stack
    items: [Any]+R+M
    f push(stack: Stack+R+M, item: Any)
        t.items.append(item)      // t — self/this
    f pop(stack: Stack+R+M): Any
        t.items.isEmpty() & e StackUnderflow()  // e — throw error
        r => t.items.removeLast()  // r — return

// w — generator loop     y — yield     b — break     c — continue
f evens(): I
    num = 0
    w+I                    // w+I = intentionally infinite
        num % 2 != 0 & c  // c — skip odd numbers
        y num              // y — yield even number
        num = num + 1

// a — async function and suspension operator
a fetchUser(id: I): User+N
    r => a db.find(id)     // 'a' here = suspend (like await)

// none — the null value (multi-letter literal)
maybeUser: User+N = none
06Modifiers

The Modifier System — ±R ±M ±N +V +C +A +F

Modifiers are single letters prefixed with + or -, placed after the type. Three binary axes plus three hardware domain modifiers. All default to the - (cheapest, safest) option.

±R
Ownership

-R Local, stack-resident, non-shared. Zero ARC cost.
+R Heap-resident, ARC-managed, shareable.

default: -R
±M
Mutability

-M Immutable through this reference.
+M Mutable. Proxy-mediated when +R.

default: -M
±N
Nullability

-N Guaranteed non-null. No null check emitted.
+N May be none. Must handle before use.

default: -N
+V
Vectorize

SIMD on CPU · warp execution on GPU. Applies to numeric types: N32+V, I8+V, I16+V. Invalid on L, S, B — compile error.

default: scalar
+C
Cache Domain

Near-memory, cache-line aligned. Compiler emits prefetch hints. Useful for hot loop data.

default: no hint
+A
Async Domain

Survives fiber suspension. Saved to fiber frame at yield. Without +A, value stays on stack only.

default: -A
The zero-annotation default is -R -M -N: local, immutable, non-null. This is simultaneously the cheapest and safest. Every + is a deliberate, visible trade-off. Nothing expensive happens without a + in the type.
Umodifiers.u
// -R -M -N default: stack, immutable, non-null — zero cost
f process(user: User, count: I): S   // all -R -M -N
    r => "done"

// +N nullable — result may be none
f findUser(id: I): User+N   // +N: may return none
    r => db.find(id)

// +R -M shared immutable — safe to broadcast, no lock needed
config: Config+R-M = loadConfig()
workers.x(worker => worker.configure(config))  // zero-copy broadcast

// +R +M shared mutable — proxy-mediated writes
cache: Cache+R+M = Cache() +M(MVCC)
cache.set(key, val)   // routed through MVCC proxy

// +V: vectorized execution (SIMD on CPU, warps on GPU)
simdVec: [N32]+V = loadData()                  // CPU SIMD — data stays on CPU
gpuVec:  [N32]+R(GPU) = simdVec.c(+R(GPU))   // copy to GPU device memory
runKernel(gpuVec)                                 // dispatches as GPU warps

// same matrix type, different location — same API
weights: [[N32]]               = loadModel()   // CPU scalar
weights: [[N32]]+V            = loadModel()   // CPU SIMD
weights: [[N32]]+R(GPU)       = loadModel()   // GPU device memory
weights: [[N32]]+R(Network)   = loadModel()   // remote cluster

// +A survives fiber suspension
a handle(): Response
    buf: [L]           = alloc(4096)  // -A: stack-local, NOT saved at suspension
    state: Req+R+A   = buildReq()   // +A: saved to fiber frame
    result = a net.get(state)          // suspend — only state saved, not buf
    r => parse(result, state)

The 8-Combination Matrix

CombinationContractAllocSyncNull chkARC
-R -M -Nlocal immutable non-nullstacknonenonenone
-R -M +Nlocal immutable nullablestacknoneon usenone
-R +M -Nlocal mutable non-nullstacknonenonenone
-R +M +Nlocal mutable nullablestacknoneon usenone
+R -M -Nshared immutable non-nullheaplock-freenoneinteger
+R -M +Nshared immutable nullableheaplock-freeon useinteger
+R +M -Nshared mutable non-nullheapproxynoneinteger
+R +M +Nshared mutable nullableheapproxyon useinteger
05.5Language

Namespaces & Modules — o

The o keyword ("outside") handles both import and export. Dots in class and function names are free namespacing — d Foo.Bar is simply a class named Foo.Bar, no special mechanism needed. o declares which namespace a file contributes to, and how to bring namespaces into scope.

Importing — four forms

Uimport forms
o Math.Linear              // qualified: Linear.matmul, Linear.Foo available
o Math.Linear.*            // glob: matmul, Foo available as bare names
o Math.Linear (            // scoped qualified: only inside this block
    result = Linear.matmul(a, b)
)                          // Linear no longer in scope here
o Math.Linear.* (          // scoped glob: only inside this block
    result = matmul(a, b)  // bare name works here
)                          // matmul no longer in scope here
Why not .* by default? Two modules both exporting transform or parse create silent conflicts. The explicit .* is a visible declaration: "I know I'm unpacking everything into this scope."

Exporting — o Name => (...)

The => direction makes intent unambiguous: this block produces into the namespace. Multiple files can contribute to the same namespace — open extension, like traits.

Uexport / namespace declaration
// file: math/linear.u

f _helper(a: [N32+V], b: [N32+V]): N  // _ prefix → private, never exported
    a.reduce((acc, i, v) => acc + v * b[i], 0.0)

o Math.Linear => (

    d Result              // becomes Math.Linear.Result
        value: [[N32+V]]
        error: N

    f matmul(a: [[N32+V]], b: [[N32+V]]): [[N32+V]]  // t absent → +G inferred
        r => a.x((i, row) => row.x((j, _) => _helper(row, b.col(j))))

    f scale(factor: N): [[N32+V]]              // t used below → instance method
        r => t.x((i, row) => row.x((j, v) => v * factor))

)

Static vs instance — inferred from t

The compiler scans the function body for the keyword t (self). Present anywhere → instance method. Absent → +G (static) inferred. No annotation needed. A function with no t has no business being an instance method — the rule is correct by definition, not just convenient.

Ut-inference.u
o Geometry => (
    d Circle
        radius: N

    f unitCircle(): Circle         // no t → +G, called as Geometry.unitCircle()
        r => Circle({ radius: 1.0 })

    f area(): N                     // t used → instance method
        r => Math.PI * t.radius ^ 2

    f scale(factor: N): Circle     // t used → instance method
        r => Circle({ radius: t.radius * factor })
)

// static callable on instance as sugar:
c = Geometry.unitCircle()         // direct
c = someCircle.unitCircle()       // also valid — desugars to above

Multi-file namespaces — open extension

Uextension.u
// math/linear_basic.u
o Math.Linear => ( f matmul(...) )

// math/linear_advanced.u — extends same namespace
o Math.Linear => ( f svd(...)  f lu(...) )

// conflict (same function name in two files) = compile error

External packages

Upackage.u
o "github.com/u-lang/math"   // external — resolved by package manager
o "./utils"                  // relative — exposes utils/*.u namespaces
6.5Type System

+F — The Function Domain Modifier

The modifier set gains a seventh dimension: +F, the function domain. All modifiers are commutative — the set is unordered. Three forms: bare, nullary, and typed.

Modifier Commutativity — The Fundamental Rule

Order never matters. Modifiers form a set, not a sequence. T +R +M and T +M +R are identical. T +F +A +R and T +R +A +F are identical. The parser normalizes to canonical order for display; semantics are always position-independent.

The Complete Seven-Dimension Modifier Set

ModifierDomainMeaningDefault
±ROwnership-R: stack-local · +R: heap, ARC-R
±MMutability-M: immutable · +M: mutable, proxy-mediated-M
±NNullability-N: non-null · +N: may be none-N
VVectorized float
+CCacheNear-memory, prefetch
+AAsyncAsync domain: pending or suspendable
+FFunctionCallable producing the base type T

Three Forms of +F

FormMeaningUse case
T +F
bare — no parens
Any callable producing T. Input types unspecified. Compiler enforces T's full modifier stack without constraining inputs. Heterogeneous callable collections, factories, hooks
T +F()
explicit nullary
Explicitly zero-argument callable producing T. Thunks, lazy initializers, event callbacks
T +F(S)
typed args
Function taking S, producing T. Full type information. Typed callbacks, typed transformers
Bare +F accepts any callable producing T. The compiler still enforces everything about the output — +N (nullable handling), +R (heap allocation), +A (async await) — without knowing or caring what arguments the callable takes. A collection typed [Config +R +F] guarantees every callable returns a shared Config, no matter its input shape.

+F in Practice

Ufunction_modifier.u
// ── Bare +F: heterogeneous callables, homogeneous output ──────
hooks: [Config+F] = []        // any callable producing Config
hooks.append(loadFromFile)       // Config +F(path: S)
hooks.append(loadFromEnv)        // Config +F()
hooks.append(loadWithDefault(b)) // Config +F() +R (closure)
// compiler: every result is Config — enforces modifiers on output
configs: [Config] = hooks.x(hook => hook(...))

// ── +F(): explicit nullary ────────────────────────────────────
thunk: Config+F() = () => Config.default()
cfg = thunk()                    // called with zero args

// ── +F(S): fully typed ───────────────────────────────────────
parser: Config+F(S) = parseConfig  // takes S, returns Config
cfg = parser(rawJson)

// ── Composing with other modifiers (commutative) ─────────────
handler: Config+F(S)+R          // escaping closure
fetcher: Config+F(S)+A          // async function
maybe:   Config+F(S)+N          // nullable reference
escAsync: Config+F(S)+R+A     // escaping async closure

// Order is irrelevant — all identical:
Config+F+R+A == Config+A+R+F == Config+R+A+F  // same type

+F Compiler Oracle

TypeCompiler decision
T +F (bare)enforce full T modifier stack on output — no constraint on inputs
T +F without +Rinline or static ptr — no indirection
T +F +Rindirect call via closure vtable
T +F +Aauto-await check inserted at call site
T +F +R +Avtable call + auto-await
T +F +Nnull check before call

+F +A — Async Function Type

Config +F(S) +A = async function taking S, producing Config. When called without a: auto-await, Config delivered transparently. When called with a: Config +A pending.

Commutativity confirms: +F +A = +A +F = one type, one meaning. "Async function returning T" and "function returning T +A" collapse — the call site determines which you get.

+F +R — The Bound Closure Type

A method closure capturing t (self) needs heap allocation to outlive its call frame — that's exactly +F +R. No separate bound-method concept needed.

d Server
    f getHandler(server: Server+R): Response+F(Request)+R
        r => req => t.handleRequest(req)   // captures t — needs +R

handler: Response+F(Request)+R = server.getHandler()
route.register(path, handler)       // stored in routing table, survives

Bare +F for Dependency Injection and Plugin Systems

// Plugin system: all plugins produce S, inputs vary
d PluginRegistry
    plugins: Map   // keyed by name

    f register(registry: PluginRegistry+R+M, name: S, plugin: S+F)
        t.plugins[name] = plugin   // S +F: any callable producing S

    f run(registry: PluginRegistry, name: S): S+N
        plugin: S+F+N = t.plugins[name]
        r => plugin & plugin(...)   // null-guard + call
// Compiler: result is S, handling enforced regardless of plugin internals
07Types

Classes, Inheritance & Policy — d, t, :

Classes use d. No separate struct keyword — a -R instance compiles identically to a C struct on the stack. t is self inside methods. : introduces a base class. No multiple inheritance.

The +M(policy) modifier declares how +R +M instances are mediated — either as a class-level default or overridden at the binding site.

Uclasses.u
// Basic class
d Point
    x: N
    y: N
    f distance(other: Point): N
        dx = t.x - other.x       // t = self
        dy = t.y - other.y
        r => (dx*dx + dy*dy).sqrt()

// Inheritance — : introduces base class, no multiple inheritance
d Animal
    name: S
    f speak(): S
        r => "..."

d Dog : Animal           // Dog inherits from Animal
    breed: S
    f speak(): S
        r => "Woof"          // override

// +P — policy modifier, class-level default
// all +R +M instances of Counter are Actor-mediated by default
d Counter+M(Actor)
    value: I
    f increment(t: Counter+R+M)
        t.value = t.value + 1

// Caller: no a needed — fiber handles Actor dispatch transparently
counter: Counter+R+M = Counter()
counter.increment()    // enqueued and serialized by Actor policy

// Binding-site override — same class, different policy
c2: Counter+R+M+M(MVCC) = Counter()   // override to MVCC

// Policy + inheritance combined
d LiveCounter+M(Actor) : Counter
    lastUpdated: S
Mutation requires +M at every step. +M on a binding grants mutation rights to +M properties. A -M property is const after initialization — the binding's +M does not override it. The most restrictive wins at each property access.
Prevents at compile time: const mutation. cfg.host = "x" where host is -M is a type error regardless of whether cfg is +M. The property's declaration wins.

Copy semantics — .c() is shallow by declaration

When you call .c() on a class instance:

The class definition itself declares copy semantics through property modifiers — no separate clone() protocol needed. This is exactly PHP's clone behavior: inline values copy, references share.

Ucopy-semantics.u
// Inline nested structs — -R means value-embedded, copies recursively
d Vec2                       // small value type, always -R by default
    x: N
    y: N

d Material+R(RAII)         // GPU resource — always lives on heap
    shader: S
    f drop(t: Material+R)    // called automatically when last ref drops
        gpu.release(t.shader)

d Mesh
    origin:   Vec2             // -R inline: Vec2 embedded, copies by value
    count:    I                // -R inline: scalar, copies by value
    vertices: [Vec2]+R      // +R heap array — reference shared on .c()
    normals:  [Vec2]+R      // +R heap array — reference shared on .c()
    material: Material+R    // +R heap object — reference shared on .c()

a = Mesh{ origin: Vec2{ x: 0.0, y: 0.0 }, count: 3,
          vertices: [...], normals: [...],
          material: Material("pbr") }
b = a.c()

// b.origin   → new Vec2 (inline — x and y duplicated)
// b.count    → 3 (scalar copy)
// b.vertices → same heap array as a.vertices (reference shared)
// b.normals  → same heap array as a.normals  (reference shared)
// b.material → same Material as a.material   (RAII refcount++)

b.origin.x = 1.0    // OK — b.origin is its own copy
a.origin.x           // still 0.0 — unaffected

// Want independent vertices? Copy the array explicitly:
b.vertices = a.vertices.c()   // b now has its own vertex buffer

// +R on a local binding: heap-allocated, shareable via +R properties
mat: Material+R = Material("wireframe") // heap, refcount=1
c: Mesh+R+M = Mesh(...)
c.material = mat    // OK: mat is +R — can be shared, refcount→2

// +R cannot reference a plain -R stack var — no refcount, compile error
plain: Vec2 = Vec2{ x: 1.0, y: 2.0 }  // -R stack
c.material = plain   // ❌ types differ: +R expected, -R given

Method overrides and explicit parent calls

To override a method, redefine it in the subclass. To call the parent version, use ParentClass.method(args)t is passed implicitly. There is no super keyword and no auto-super magic.

The one inference point: t present in body → instance method. t absent → +G (static). This applies to overrides too — a static parent method overridden with a static child method just works. If a parent changes a method from static to instance, the subclass breaks at the next compile — surfacing the breaking change immediately, never silently at runtime.
Uoverrides.u
d Bar
    name: S
    f __construct(a: I)
        t.name = a.toString()
    f greet(): S                    // instance — t used
        r => "Hello from " + t.name
    f version(): S                  // static — no t
        r => "Bar v1"

d Foo : Bar
    x: S = "default"

    f __construct(a: I, b: S)
        Bar.__construct(a)            // explicit — t passed implicitly
        t.x = b                       // own init after parent

    f greet(): S                    // overrides instance method
        base = Bar.greet()           // call parent — t implicit
        r => base + ", also " + t.x

    f version(): S                  // overrides static — no t here either
        r => Bar.version() + "-Foo"

// Not calling Bar.__construct is valid — deliberate choice
// Compiler warns if __construct never calls parent's
07.5Types

Type Hierarchies — U's Enum Replacement

U has no enum keyword. Instead, named subtypes with dot-namespace syntax serve the same purpose — and more, because they participate in the event system, error system, and type dispatch natively.

The pattern: d Foo.Bar : Foo declares a type Bar namespaced under Foo, inheriting from Foo. Zero-field types are auto-singletons — Color.Red as an expression IS the value, no () needed.
Utype-hierarchy.u
// Define a base type and its variants
d Color
d Color.Red   : Color    // zero-field → auto-singleton
d Color.Green : Color
d Color.Blue  : Color

// Color.Red IS the value — no () needed
bg: Color = Color.Red
bg : Color.Red    // L true — isinstance
bg : Color        // L true — base type

// Variants can carry data (unlike traditional enums)
d Shape
d Shape.Circle    : Shape  { radius: N }
d Shape.Rect      : Shape  { width: N, height: N }
d Shape.Triangle  : Shape  { base: N, height: N }

// Dispatch via : type test
area = shape : Shape.Circle
? Math.PI * shape.radius ^ 2
! shape : Shape.Rect
? shape.width * shape.height
! shape.base * shape.height / 2

// Hierarchical namespaces
d NetworkError
d NetworkError.Transport         : NetworkError
d NetworkError.Transport.Timeout : NetworkError.Transport
d NetworkError.Transport.Refused : NetworkError.Transport
d NetworkError.Protocol          : NetworkError
d NetworkError.Protocol.SSL      : NetworkError.Protocol

The pit of success: typed middleware over switch/match

U has no switch or match keyword. This is deliberate. Enumerating cases in a closed dispatch block couples the caller to every variant — adding a new case requires finding and updating every dispatch site. The open alternative is typed handlers:

Udispatch-vs-middleware.u
// ❌ closed dispatch — every new Shape breaks this
area = shape : Shape.Circle   ? Math.PI * shape.radius ^ 2
     ! shape : Shape.Rect     ? shape.width * shape.height
     !                           0.0   // ← new shapes silently return 0

// ✅ open dispatch — new Shape? add one handler, no existing code changes
renderer.x((s: Shape.Circle)   => drawCircle(s.radius))
renderer.x((s: Shape.Rect)     => drawRect(s.width, s.height))
renderer.x((s: Shape.Triangle) => drawTriangle(s.base, s.height))
// add Shape.Hexagon later? one new line, zero regressions

// Same pattern already used for errors:
e.x((err: NetworkError.Transport.Timeout) => retry())
e.x((err: NetworkError.Protocol.SSL)       => alertSecurity())
// errors and enums are the same pattern. one mechanism.

Static fields — +G

+G is for class-level (static) fields — shared across all instances, not per-instance. Separate concern from type hierarchies.

Ustatic-fields.u
d Config
    apiUrl:  S +G    = "https://api.example.com"  // static constant
    timeout: I +G    = 30                        // static constant
    calls:   I +G+M  = 0                         // static mutable counter
    host:    S       = "localhost"               // instance field (no +G)
07.6Types

Interfaces & Abstract Methods — !

Append ! to a class declaration for a pure interface. Append ! to an individual method for an abstract method in a partial abstract class. Interfaces constrain instances — methods in interfaces are never static, never have properties.

Rules:
d Foo! — pure interface, all methods abstract, no instantiation
f method()! — abstract method in a partial class
Interface methods are always instance methods — +G not allowed
No properties in interfaces — only method signatures
Compiler verifies all abstract methods are implemented by concrete subclasses
Uinterfaces.u
// Pure interface — all methods abstract
d Drawable!
    f draw(ctx: Context)        // abstract — no body
    f bounds(): Rect            // abstract — no body

// Partial abstract class — some concrete, some abstract
d Animal
    f speak()!                   // abstract — subclass must implement
    f breathe()                  // concrete — inherited as-is
        t.lungs.cycle()

// Concrete implementation
d Circle : Drawable
    radius: N
    f draw(ctx: Context)
        ctx.drawCircle(t.center, t.radius)
    f bounds(): Rect
        r => Rect({ x: t.center.x - t.radius, y: t.center.y - t.radius,
                    width: t.radius*2, height: t.radius*2 })

// Type test works for interfaces
shape: Drawable = Circle({ radius: 5.0 })
shape : Drawable   // L true
shape : Circle     // L true — concrete type also works

// Static interface methods: not allowed — interfaces are for instances
// Use type hierarchies for static polymorphism instead:
d Shape.Circle : Shape
    f create(radius: N): Shape.Circle   // static — called as Shape.Circle.create(5.0)
        r => Shape.Circle({ radius })

Untyped parameters — not allowed

Every parameter must have a type annotation. f process(data) is a compile error. No untyped escape hatch — the type system has no any. Use interfaces or class hierarchies for polymorphism, __unserialize for external data.

Utyped-params.u
f good(data: [N32+V])          // ✓ typed

// f bad(data)  ← ✗ compile error: no type annotation
f fetch(url: S, { timeout: I = 30, verbose: L = false })
// { timeout }  ← ✗ compile error: missing type in named param
08Ergonomics

Maps — {K: V}

When the shape of data is known at compile time, use a typed class. When keys are determined at runtime — JSON payloads, plugin config, metadata — use a {K: V} map.

In v1, arrays [T] and maps {K: V} are the only parameterized types. No full generics yet, but these two cover most needs. Maps accept all modifier annotations and have the same iteration methods as arrays. Iteration order is guaranteed to be insertion order — this is a language guarantee, not an implementation detail. Map iteration handlers receive (key, value) pairs.

Key types. Any type can be a map key if it supports hash and equality. Primitives (S, I, N, L) work automatically. For classes, implement f __hash(): I and f __equals(other): L — these make any class usable as a map key. Override both or neither — the compiler enforces this. If you declare {MyClass: V} on a class with __equals but no __hash, it is a type error.
Umaps.u
// Map literal — runtime key-value store
meta: {S: Any} = { "author": "Alice", "version": 3 }

// Any comparable type as key
userById:  {I: User}    = {}   // integer keys
byPoint:   {Point: I}   = {}   // class key — Point must implement hash() and ==

// Dynamic access — returns +N (key may not exist)
author:  S+N = meta["author"]
version: I    = meta["version"] ?? 1
"author" in meta   // L — O(1) membership

// Modifiers — same as classes
roConfig:  {S: S}+R-M         = loadConfig()   // shared immutable
liveState: {S: Any}+R+M+M(MVCC) = {}            // shared mutable + MVCC

// Class as key — implement hash() and ==
d Point
    x: I, y: I
    f __hash(): I              => (t.x * 31 + t.y) * 31
    f __equals(other: Point): L => t.x == other.x & t.y == other.y

grid: {Point: S} = {}   // works because Point has __hash and __equals
grid[Point({ x: 0, y: 0 })] = "origin"

Map iteration — same methods as arrays

All map iteration receives (key, val) pairs. Three-signal convention: none = include/continue, false = skip, true = stop.

Umap-iteration.u
// .x() — side effects
meta.x((key, val) => log("{key}: {val}"))

// .filter() — returns new Map
strings = meta.filter((key, val) => val : S ? none ! false)

// .map() — transform values, keys preserved
upper = meta.map((key, val) => val : S ? val.uppercase() ! val)

// .find() — returns (K, V) +N
hit: (key: S, val: Any)+N = meta.find((key, val) => val == target ? none ! false)

// .reduce() — fold
total = prices.reduce((acc, key, val) => acc + val, 0.0)

// Promise-aware: .all() and .any() work on {K: V +A} maps
fetching: {S: Response+A} = endpoints.map((key, url) => a fetch(url))
results:  {S: Response}   = fetching.all()   // await all, same keys preserved
first:    Response+N+A  = fetching.any()   // first to resolve

Maps as Sets — {K: L}

A {K: L} map where the value is a boolean sentinel is a Set. The in operator gives O(1) membership. Set operations fall out from .filter().

Uset-via-map.u
admins: {S: L} = { "alice": true, "bob": true }
"alice" in admins   // true — O(1)
admins["carol"] = true    // add
admins["bob"] = none       // remove

editors: {S: L} = { "bob": true, "carol": true }
both = admins.filter((key, _) => key in editors ? none ! false) // intersection
only = admins.filter((key, _) => key in editors ? false ! none) // difference
all  = admins.c(+M).merge(editors)                                 // union

// No Set<T> type in v1 — no need for one.
// {K: L} maps with the in operator cover all set semantics.

Map iteration — (key, value) pairs, insertion order guaranteed

Umap-iteration.u
scores: {S: N} = { "alice": 9.8, "bob": 7.4, "carol": 8.1 }

// .x() — iterate (key, value) in insertion order
scores.x((score, name) => print(name + ": " + score.toString()))  // value first
// alice: 9.8 / bob: 7.4 / carol: 8.1 — guaranteed order

// .map() — transform values, key passed through
grades: {S: S} = scores.map((score, name) => score >= 9.0 ? "A" ! "B")

// .filter() — keep entries where predicate holds
topScores: {S: N} = scores.filter((score, name) => score >= 9.0 ? none ! false)

// .all() / .any() — synchronous predicates
allPassed: L = scores.all((score, name) => score >= 5.0)   // true
anyHonours: L = scores.any((score, name) => score >= 9.5)  // false

// .reduce() — fold over entries
total: N = scores.reduce((acc, score, name) => acc + score, 0.0)

// .find() — first matching entry, returns value+N
winner: N+N = scores.find((score, name) => score >= 9.0 ? none ! false)

// .keys() / .values() / .entries() — explicit projection
names:   [S]        = scores.keys()     // [S] in insertion order
vals:    [N]        = scores.values()   // [N] in insertion order
pairs:   [[S, N]]  = scores.entries()  // [[key, value], ...] in insertion order
Insertion order is a language guarantee. { "a": 1, "b": 2 } always iterates a first, b second — in literals, after insertions, after mutations. The compiler implements maps as ordered hash maps. This guarantee makes map iteration testable and predictable.

Why Symbol, WeakMap, and a separate Map type are unnecessary

JS conceptWhy it exists in JSU equivalent
MapObjects only allow string keys; Map allows any type{K: V} with any key type — it IS the map
WeakMapAttach metadata without preventing GC collectionARC: objects have deterministic lifetimes. Weak refs if needed are a modifier, not a separate collection type.
SymbolUnique non-string keys, private properties, protocol hooksType system (uniqueness), class visibility (privacy), .x() protocol (iteration hooks)
SetUnique-value collection with O(1) membership{K: L} map + in operator — same semantics, no separate type
Generics in v1: [T] and {K: V}. These are the only parameterized types — and they compose. [[T]] is a matrix, [[[T]]] a rank-3 tensor, arbitrary rank by nesting. No dependent types, no rank parameters, no new mechanism. [[V32]]+R(GPU) is a GPU matrix; same story for any element type and any location policy.
08.5Protocol

Magic Methods

Six double-underscore methods form the v1 protocol. The compiler recognises them by name and calls them automatically.

MethodSignatureCalled whenConstraint
__constructf __construct({...})Object created via ClassName({...}). If absent, compiler auto-generates one from declared fields.No auto-super. Call parent explicitly: Bar.__construct(args)t passed implicitly.
__hash(): IObject used as map key — bucket placementMust be consistent with __equals: if a.__equals(b) then a.__hash() == b.__hash()
__equals(other: T): La == b operator; map key collision resolutionMust be consistent with __hash. Override both or neither.
__serialize(): SObject serialised to string (storage, network)Should round-trip with __unserialize
__unserialize(data: S): TObject reconstructed from string — class-level factoryDefined inside the class; compiler treats it as static. Should round-trip with __serialize.
__merge(base: T, theirs: T): TMVCC conflict resolution — two fibers write same objectbase = common ancestor, t = your changes, theirs = other fiber's commit. Without it: last write per subtree wins. +M(CRDT) = MVCC with commutative idempotent __merge.
Default behaviour. Classes that define neither __hash nor __equals get structural defaults: all fields compared for equality, hash derived from all fields. This means any class can be used as a map key without writing anything — override only when you need a subset of fields to determine identity.
Umagic-methods.u
d User
    id:    I
    name:  S
    email: S

    // Identity by id only — name and email changes don't affect map keys
    f __hash(): I            => t.id
    f __equals(other: User): L => t.id == other.id

    // Serialization — round-trips through __unserialize
    f __serialize(): S
        r => "{t.id}|{t.name}|{t.email}"

    // Class-level factory — called as User.__unserialize(str)
    f __unserialize(data: S): User
        parts = data.split("|")
        r => User{ id: parts[0].toI(), name: parts[1], email: parts[2] }

// Usage
userMap: {User: Session} = {}      // User as map key — uses __hash and __equals

alice = User{ id: 1, name: "Alice", email: "a[at]x.com" }
str   = alice.__serialize()            // "1|Alice|a[at]x.com"
back  = User.__unserialize(str)        // User({ id:1, name:"Alice", ... }
alice == back                          // true — same id, __equals passes


    // Conflict resolution — called when two fibers write same Config
    f __merge(base: Config, theirs: Config): Config
        Config{
            host: theirs.host,             // theirs wins for host
            port: t.port,                  // keep my port
            flags: t.flags.merge(theirs.flags)  // union flags map
        }

// Default: no magic methods needed for simple value types
d Color { r: I, g: I, b: I }         // structural __hash and __equals auto-generated
colorCount: {Color: I} = {}            // just works
09Safety

Null Safety

Non-null is the default. +N explicitly opts into nullability. The compiler never emits a null check for -N values — the type proves it unnecessary.

Unull_safety.u
// -N default: can never be null — no check emitted
name:  S = "Alice"    // -N: compiler skips null check entirely
count: I = 42

// +N: may be none — must handle before use
user: User+N = findUser(id)

// ?? coalesce — provide fallback value
display = user?.name ?? "Guest"   // S -N (narrowed to non-null)
port    = config.port ?? 8080

// & guard — execute only if non-null
user & sendWelcomeEmail(user)      // runs only if user != none

// ?. safe chain — none propagates through the whole chain
city: S+N = user?.address?.city   // +N if any step is none
name: S    = user?.address?.city ?? "unknown"  // -N with fallback
// Compiler emits exactly 2 null tests (user, address)
// city is -N on User.address, so no test there

// Early return on none — clean handler pattern
f handleLogin(uid: I): Response+N
    user = findUser(uid)
    user ?? r => Response.notFound()  // early return if none
    // user is narrowed to User -N here — no more null checks needed
    session = createSession(user)
    r => Response.ok(session.token)

// Compile error: cannot dereference +N without handling
bad = user.name   // ❌ COMPILE ERROR: user is +N — must check first
Prevents at compile time: null dereference. name.length() where name: S +N is a type error — the compiler requires a null guard before any method call or property access on a +N value.
10Control Flow

Conditionals — ?! & | ?? :

No if, no else, no switch, no match. Five operators handle all conditional logic.

OperatorSemanticsUse case
cond ? a ! bif cond then a else b — cond must be LTernary / if-else. ? = "yes", ! = "not"
expr : Typetrue if expr is-a Type — returns LType test, dispatch
a & bif a then b — a must be LGuard: run b only if a is true
a | bfirst truthy value — any typeFallback chain
a ?? bb if a is none, else a — any +N typeNull coalesce
? and ! are a natural pair. The ternary reads "is it? yes: … not: …". Freeing : makes it available for type tests (expr : Type) and inheritance (d Dog : Animal) — both mean is-a, consistently.
Uconditionals.u
// Syntax rules: ? and ! at condition indent level
// Arbitrary spaces after ? or ! to align. Continuations indented.

// Inline
label = count > 1 ? "items" ! "item"

// Multi-line block — last expr in each branch is the value
greeting = time < 12
? name = user.firstName
    "Good morning, " + name      // indented → still in ? branch
! name = user.nickname | user.firstName
    "Good afternoon, " + name    // indented → still in ! branch

// Nested
status = user != none
?   user.active
    ?   "active user"
    !   "inactive user"
!   "no user"

// Switch-like: ! at condition level, spaces after ! for alignment
grade = score >= 90 ? "A"
!       score >= 80 ? "B"
!       score >= 70 ? "C"
!                     "F"

// : type test — expr : Type returns B
shape : Circle    & drawCircle(shape)
shape : Rectangle & drawRect(shape)

// Type dispatch with ?! — replaces switch/match/instanceof
area = shape : Circle
?   Math.PI * shape.radius ^ 2
! shape : Rectangle
?   shape.width * shape.height
!   0.0

// & | ?? — short-circuit value operators
admin  & showAdminPanel()            // guard
valid  & r => compute()             // conditional return
label = user.nickname | user.firstName | "Anonymous"  // fallback
port  = config.port ?? 8080          // null coalesce
Three syntax rules for ?!:
1. ? and ! sit at the same indent level as the condition line.
2. Arbitrary spaces after ? or ! before first content — for alignment.
3. Continuation lines must be indented (tab) to stay in the branch.
Boolean enforcement on ? condition and & left side. 5 ? 2 ! 8ERROR: 5 is I, not B. Convert explicitly: L(expr) or !!(expr). | and ?? accept any type — they select values, not test conditions.
11Control Flow

Iteration — Array Methods

No for, no while. All iteration uses named array methods — all native, all bounded, all sharing the same underlying engine. The three-signal convention applies to any method that searches or filters:

Return from handlerMeaning
none (default)Include this item, continue
falseSkip this item, continue
trueStop here, include this item
MethodReturnsNotes
.x(handlers)Each. Passes (value, key) — declare one param to drop the key. none=continue, true=stop chain.
.filter(pred)[T]none=include, false=skip, true=stop-and-include. Like JS filter but with early-exit.
.map(fn)[U]Passes (value, key). Return value is the new element.
.reduce(fn, init)U(acc, item) => newAcc. Standard fold.
.find(pred)T +NStops at first Non-false return. Returns the item or none. Caller knows what they called.
.all(pred)LSynchronous: true if every element satisfies pred. Short-circuits on first false.
.any(pred)LSynchronous: true if any element satisfies pred. Short-circuits on first true. any(f) = !all(!f) by De Morgan.
.all()[T]+AAsync join on [T+A] only. Awaits all concurrently; results in input order.
.any()T+N+AAsync race on [T+A] only. Resolves with first completed value or none.
Uiteration.u
// .filter() — three signals
active = users.filter((user) => user.active ? none ! false)     // include if active
first10 = items.filter((item) => (
    taken = taken + 1
    taken <= 10 ? none   // include
    !            true     // stop
))

// .map() — return value is the new element
names = users.map((user) => user.firstName + " " + user.lastName)
doubled = nums.map((num) => num * 2)

// .reduce() — fold
total = orders.reduce((acc, order) => acc + order.amount, 0.0)

// .find() — returns T +N, caller knows what they called
user: User+N = users.find((user) => user.id == target ? none ! false)
user != none & process(user)  // or:  user ?? defaultUser

// .all() — Promise.all: await all +A values, results in input order
pending: [S+A] = urls.map((url) => a fetch(url))    // N concurrent fibers
pages:   [S]    = pending.all()                          // await all, ordered

// .any() — Promise.race: returns T +N +A, caller knows
winner: S+N+A = pending.any()  // first to resolve, or none
winner != none & render(winner)

// .x() stays for events, errors, and side-effect iteration
users.x((user) => log(user.name))    // for-each with side effect
e.x(MyLib.networkPolicies)           // error handler registration
clicks.x((evt) => handleClick(evt))  // event subscription
.all() and .any() are native methods, not library functions. They are promise-aware at the language level: mixed [S, S +A, S +A] arrays work naturally — regular values pass through instantly, +A values are awaited concurrently. Results always come back in input order. If any +A value rejects, it throws — caught by e.x() handlers. No .catch() needed.
11.5Control Flow

Ranges — .. and ...

Range syntax produces array values directly — no special Range type. [0..10] is an array literal. Arithmetic on it is element-wise, same as any array. The compiler recognizes range-derived arrays in .x() and emits a tight loop without materializing the array.

[a...b] — inclusive both ends (like Ruby ..)
[a...b] — exclusive end (like Ruby ...)
Arithmetic is element-wise: [0..4]*2 = [0,2,4,6,8]
Urange syntax
// Inclusive — both ends included
arr = [1..5]         // [1, 2, 3, 4, 5]
arr = [5..1]         // [5, 4, 3, 2, 1]  — start > end counts down

// Exclusive end
arr = [0...10]        // [0, 1, 2, ..., 9]  (not 10)
arr = [0..10]         // [0, 1, 2, ..., 10] (includes 10)

// Arithmetic is element-wise — produces new array
[0..4]*2             // [0,1,2,3,4]*2 = [0, 2, 4, 6, 8]
[4..0]*2             // [4,3,2,1,0]*2 = [8, 6, 4, 2, 0]
[0...10]*2           // [0,1,...,9]*2  = [0, 2, 4, ..., 18]

// Offset pattern — scalar arithmetic lifts over array
(foo - [1..5]*2).x((i, v) => process(v))  // foo-2, foo-4, foo-6, foo-8, foo-10
(base + [0...n]*stride).x((i, v) => arr[v])  // stride pattern, n steps

// .x() on range — compiler emits tight loop, no allocation
[0...n].x((i, v) => draw(v))
[0...rows].x((row) =>                   // iterate rows
    [0...cols].x((col) =>                // iterate cols
        matrix[row][col] * scale
    )
)

// Array slicing
arr[1..4]              // elements 1,2,3,4 — inclusive
arr[1...4]             // elements 1,2,3 — exclusive end
arr[4..1]              // reversed slice [4,3,2,1]
Compiler optimization. When .x() is called on a range-derived array, the compiler recognizes the pattern and emits a counter loop — same machine code as hand-written C, no array allocated.
12Control Flow

Generators — w, y, b, c

The w keyword creates a generator loop. Generators are stackless — they can only yield from the top level, not from nested function calls. (For deep suspension from any call depth, use fibers with a.) w+I declares intentionally infinite — required for infinite generators, or the compiler warns.

Ugenerators.u
// Bounded loop with break
w
    event = nextEvent()
    event.isDone & b     // b = break
    process(event)

// Generator function — yields values lazily
f naturals(): I
    num = 0
    w+I             // w+I = intentionally infinite, no warning
        y num        // y = yield
        num = num + 1

// Fibonacci
f fibonacci(): I
    prev = 0
    curr = 1
    w+I
        y curr
        next = prev + curr
        prev = curr
        curr = next

// Consume a generator with .x()
fibonacci().x(n => n > 1000 & b)  // stop when > 1000

// continue skips current iteration
w
    line = readLine()
    line.isEmpty() & c          // c = continue, skip blank lines
    line.startsWith("#") & c   // skip comments
    processLine(line)
    eof() & b
13Control Flow

Error Handling — e

e is dual. At the definition site: e ErrType(...) throws. At the call site: e.x() registers typed handlers — the same .x() subscription used everywhere. Error handlers are typed event listeners.

What .x() accepts

FormBehavior
e.x(handler)Append one typed function to that type's chain
e.x(h1, h2, h3)Append all in order — equivalent to three separate .x() calls
e.x(policyArray)Spread an array of typed functions onto the chain in order — array order is defined and preserved
Later overwrites earlier, by type. The underlying structure is a type-indexed array. For a given error type, the last registered handler wins — registering a second handler for the same type replaces the first. .x() is array-set-by-type, not push. Early exit (returning true) stops only that specific type's chain — other type chains are independent. Library policies are exported as arrays, not objects — array order is defined, method iteration order on objects is not.
Uerrors.u
// ── Definition site ───────────────────────────────────────────────────
d NetworkError : BaseError { msg: S, code: I, retryable: L }
d TimeoutError : NetworkError { elapsed: I }

a f fetch(url: S): S ! NetworkError ! TimeoutError
    connected | e NetworkError{ msg: "offline", retryable: true }
    elapsed > limit & e TimeoutError{ elapsed: elapsed }
    r => body

// ── Single and multi-arg ──────────────────────────────────────────────

a f run(url: S)
    e.x(logTimeout, retryOnTimeout)    // push both onto TimeoutError chain
    e.x((err: NetworkError) => err.retryable ? retry(url) ! logAndFail(err.msg))
    result = a fetch(url)
    process(result)

// ── Library policy arrays — ordered, exported, composable ────────────

// In MyLib — exported as arrays, NOT objects (object method order is undefined)
networkPolicies: [(NetworkError)->()] = [
    logToSentry,
    notifyOncall,
    retryOnRecoverable   // order defined and preserved
]

// Call site — array spreads onto chain in order
a f run(url: S)
    e.x(MyLib.networkPolicies)        // spread array: all three appended in order
    e.x(MyLib.networkPolicies, extraHandler) // append more after the array
    result = a fetch(url)
    process(result)

// ── Pit of success — library symbols ─────────────────────────────────

e.x(
    MyLib.handleNetworkError,
    Foo.failClosedOnSyntaxError
)
result = a fetch(url)   // happy path
process(result)

// ── Early exit is per-type, independent chains ────────────────────────

e.x((err: NetworkError) => { reconnect() | (r => false) }) // false = stop NetworkError chain
e.x((err: NetworkError) => logAndFail(err.msg))  // skipped if above returns false
e.x((err: TimeoutError) => retry(url))           // TimeoutError chain unaffected

// ── The rule: declare what you originate, propagation is inferred ─────

// You ORIGINATE this throw → must declare ! NetworkError in your signature
a f fetch(url: S): S ! NetworkError ! TimeoutError
    connected | e NetworkError{ msg: "offline" }   // ← originated here
    elapsed > limit & e TimeoutError{ elapsed }      // ← originated here
    r => body

// You PROPAGATE from a callee → inferred automatically, no declaration needed
a f run(url: S): S                   // no ! needed
    e.x((err: NetworkError) => retry(url))  // handled
    r => a fetch(url)    // TimeoutError propagates — compiler infers it
                         // no ! TimeoutError required in run()'s signature

// Optional: explicitly seal your surface — compiler verifies accuracy
a f run(url: S): S ! TimeoutError   // declares AND verifies: only this escapes

// Block-scoped catching: () is your try block
result = (
    e.x((err: NetworkError) => r => fallback)  // only inside this block
    a fetch(url)
)

.x() as every — and any via De Morgan

The same chain model applies to iteration. .x() runs all handlers; returning a truthy value stops the chain early and returns that item. Returning none (the default) continues. Any is the De Morgan inverse: any(f) = !every(!f). See the Fibers section for .all() and .any() on promise arrays.

Uevery-any.u
// every — false stops chain early
allPaid     = orders.x((order) => order.paid)       // false if any not paid
noneOverdue = orders.x((order) => !order.overdue)   // false if any overdue

// any via De Morgan:  any(f)  =  !every(!f)
hasAdmin   = !users.x((user) => !user.isAdmin)    // true if any user is admin
anyOverdue = !orders.x((order) => !order.overdue)   // true if any order overdue
Static dispatch, zero-cost happy path. Handler chains are statically known in typical code. The compiler builds type-indexed dispatch tables at compile time. The happy path has zero runtime overhead. On throw: O(handlers) array scan. Stackful fibers mean no stack unwinding — as efficient as C++ zero-cost exception tables, often faster since RAII handles cleanup via refcounting.
14Concurrency

Fibers, Program-as-Graph & Promise Semantics

The program IS the dependency graph

Every +A value is a node in a dependency graph. Fibers execute when their +A dependencies resolve. The scheduler runs whatever fiber has all inputs ready, suspends fibers that are waiting, and resumes them on resolution. You write ordinary code; +A annotations declare which values cross fiber boundaries.

Rejection throws

When you auto-await a +A value that was rejected, it throws. The exception hits the e.x() handler chain — the same path used for all other errors. There is no separate "rejection handler" or .catch(). One mechanism for all failure paths.

.x() on arrays — Promise.all semantics

.x() on an array of mixed values and +A values is Promise.all-aware. Regular values pass through instantly; +A values are awaited concurrently. Results are returned in input order regardless of resolution order. The return is always [T] +A — always async, even if all inputs are already resolved.

Stop convention: none = continue, truthy = stop. The default return from any handler is none (falsy), meaning "continue". Returning any truthy value — including true — stops the chain. When stopped early, .x() returns T +A (the single value that triggered the stop). When all elements are processed, it returns [T] +A. This convention is uniform across data iteration and e.x() error/event handler chains.
Upromises.u
// Start N concurrent fibers — all run in parallel
pending: [S+A] = urls.x((url) => a fetch(url))

// all — collect all results in input order (auto-await)
pages: [S] = pending.all()       // waits for all, returns [S] in order
// time = max(fetch times), not sum

// any / race — first to resolve wins (returns T +A, not array)
first: S+A = pending.any()       // stops at first resolved — Promise.race

// .all() and .any() are just functions:
// .all() = include all, never stop     — include all, never stop
// .any() = stop at first resolved     — stop at first, return it

// Custom: stop when result passes a condition
bigPage: S+A = pending.x((page) => page.length > 10000 ? page ! none)
// returns first page longer than 10k chars, or continues

// Start as new fiber — get [S] +A back, caller continues
results: [S]+A = a pending.all()

// Rejection: if any fetch throws, e.x() handlers fire
e.x((err: NetworkError) => logAndFail(err.msg))
pages = pending.all()   // any rejection → throws → e.x() above fires

// De Morgan — any(f) = !every(!f) still works on plain arrays
hasAdmin   = !users.x((user) => !user.isAdmin)   // true if any is admin
anyOverdue = !orders.x((order) => !order.overdue)

Stack preservation

JavaScript async/await loses call frames across await points — the continuation runs in a new microtask with no knowledge of its caller. U fibers are stackful: the full call stack is preserved across suspension. An exception thrown anywhere in a fiber carries the complete trace from main through every intermediate caller.

JSlost stack
// Stack frames lost across await
async function run(id) {
  const order = await fetch(id);  // frame LOST
  await save(order);              // new stack
}
// Error in save(): trace shows
// save() internals only — run() gone
Upreserved stack
// Full stack preserved across suspension
a f run(id: I)
    order = a fetch(id)   // stack preserved
    a save(order)          // stack preserved

// Error in save(): trace shows
// save → run → main — all intact

Red-blue freedom

In JavaScript, Rust, and Python, async functions have a different "color" — once a function does anything async, every caller must become async too. U solves this: stackful fibers with +A annotations keep suspension frames minimal, and regular functions call async functions without adopting a different color.

Uno-coloring.u
f buildReport(): S              // plain f — not a f
    data: S+A = a fetchData()  // starts new fiber
    r => render(data)            // auto-awaits — no color infection
// JS: buildReport must be async, every caller must be async, cascade
15Concurrency

The a Keyword — Async Done Right

The a keyword is a behavioral capability keyword (not a modifier). It has three syntactic positions and one meaning: engagement with the async domain.

PositionSyntaxMeaning
Function declarationa f fetch()Behavioral keyword: this function may suspend. NOT a modifier. Like f or d.
Call site opt-inpending: T +A = a f()Opt into async domain. Produces T +A. Co-requires +A on LHS. Starts new fiber, caller continues.
Cooperative yielda;Yield to scheduler immediately. Requires a f declaration.
Timed yielda 5 (literal only)Yield at least 5ms (lower bound). Literal integer only. Requires a f declaration.
Modifiers on function declarations: none. a is a keyword, not a modifier. Modifiers on a declaration describe the return value and parameters only. a f fetch(url: S): S +N — the +N is on the return value, a is on the function keyword.
Ua_keyword_all_forms.u
// ── Declaration: behavioral keyword, not a modifier ──────
a f lookup(key: S): Config+N   // 'a' marks function as suspendable

// ── Form 1: Auto-await (default, zero annotation) ────────
cfg = lookup(key)              // fiber suspends transparently if needed
                               // same CALL instruction regardless of sync/async

// ── Form 2: Async opt-in (a + +A co-required) ────────────
pending: Config+N+A = a lookup(key)  // new fiber, caller continues

// ── TYPE ERRORS ───────────────────────────────────────────
cfg = a lookup(key)           // ❌ 'a' without +A on LHS
pending: Config+A = lookup(key)  // ❌ +A without 'a' at call site

// ── Cooperative yields ────────────────────────────────────
a f heavyWork()
    data.x((item, index) =>
        process(item)
        idx % 1000 == 0 & a;  // yield every 1000 items
    )

a f poll()
    w+I
        checkAndProcess()
        a 5              // yield at least 5ms (literal only)
16Concurrency

Proxy Policies — +M(policy)

All +R +M objects are proxy-mediated. The +M(policy) modifier declares how writes and reads are handled. It can appear on the class definition as a default, or at the binding site as an override. Multiple concurrent writers are possible — structurally impossible in safe Rust.

ModifierSemanticsCleanupBest for
+R(RAII)Lifetime policy on +R: drop() fires when last ref dropsImmediate, ref-countedFile handles, GPU resources, DB connections
+R(Network)Lifetime policy on +R: object lives remotely, reads/writes are RPCRemote lifetimeMicroservices, distributed objects
+R(GPU)Lifetime policy on +R: object lives in GPU device memoryGPU driverML weights, tensors, geometry buffers
+M(Actor)Write policy on +M: all mutations serialised through message queueRefcountedMutable state with concurrent writers, UI
+M(MVCC) DEFAULTWrite policy on +M: optimistic versioning — readers never block, writers create new versionOld versions freed when no readersConfig, shared state with rare writes
+M(CRDT)Write policy on +M: commutative merge — all writes eventually consistentRefcountedDistributed counters, sets, logs
+M(Mutex)Write policy on +M: exclusive lock — one writer at a timeRefcountedLow-level critical sections
Class-level vs binding-site. +P on the class definition is the default for all +R +M instances. +P at the binding site overrides it. If neither is specified, the binding site must declare one when using +R +M.
Uproxy.u
// Class-level default policy
d AppState+M(MVCC)
    data: [S]

state: AppState+R+M = AppState()  // uses MVCC from class
snap = state.c()             // instant snapshot, no lock
state.update(delta)          // serialized write through proxy

// Binding-site policy — class has no default, policy declared here
inbox: Queue+R+M+M(Actor)  = Queue()
inbox.push(msg)              // enqueued, processed in order

votes: GCounter+R+M+M(CRDT) = GCounter()
votes.increment(nodeId)      // order doesn't matter, same result

// RAII — cleanup fires when last reference drops
d FileHandle+R(RAII)
    fd: I
    f drop(t: FileHandle+R)  // called automatically on last-ref-drop
        os.close(t.fd)

// Override class default at binding site
c1: Counter+R+M          = Counter()  // uses class default
c2: Counter+R+M+M(MVCC) = Counter()  // override
17Safety

Context-Aware Templates, i18n, and JSON

Template literals are literals

The tag (html, sql, msg) must appear literally at the call site — it cannot be stored in a variable or passed as a parameter. This is what makes compile-time injection safety possible: the compiler sees the tag and applies the correct escaping rules before the program runs. A dynamic tag would mean the escaping decision is a runtime choice, which breaks the guarantee.

Not allowed: tag = html; page = tag`{input}` — the tag must be the literal word html, sql, or msg at the call site, not a variable. The same reason "hello" is a string literal and not a variable named string.
Utemplates.u
// html — {expr} auto-escaped, XSS structurally impossible
page = html`<h1>{title}</h1><p>{userInput}</p>`  // both escaped ✅
frag = html`<img src="{avatar?}">`                 // {expr?} = omit if none
raw  = html`<div>{{trusted}}</div>`               // {{expr}} = raw unsafe ⚠

// sql — params sent separately, SQL injection impossible
rows = sql`SELECT * FROM users WHERE id = {uid}`
// → sends uid as $1 parameter, never interpolated into the query string

// msg — locale keys and placeholders verified at compile time
hi  = msg`{greeting, name: user.name}`  // ✅ key + placeholder matched
err = msg`{welcome}`                    // ❌ compile error: key not in bundle
er2 = msg`{greeting}`                   // ❌ compile error: {name} missing

// Tags are literals — cannot be stored or passed
tag = html                     // ❌ compile error: html is not a value
f render(tag, data) = tag`{data}` // ❌ compile error: dynamic tags disallowed

JSON with comments

U's JSON literals support /* */ block comments (and // line comments) via a regex-strip pass before parsing. This is the same approach VS Code uses for .json config files. The comments never reach the JSON parser — they are stripped at compile time for inline literals, or at load time for json-tagged strings.

Ujson-comments.u
// JSON literals in U support /* */ and // comments
config = {
    /* database */
    "host": "localhost",   // local dev
    "port": 5432,           /* default postgres port */
    "name": "mydb"
}

// Also works in json`` template literal for loading dynamic content
data = json`{rawString}`    // strips comments from rawString before parsing
Prevents at compile time: XSS and SQL injection. Functions that accept Html or Sql types reject raw S strings — the only way to produce Html is via html`...` which escapes all interpolations.
18Safety

Arithmetic, Bitwise, and Power Operators

Bounds checks, divide-by-zero checks, and overflow detection are on by default. ! is the universal opt-out suffix. Since & and | are the guard and fallback operators, bitwise operators use the ~ prefix — keeping single-character operators for the more common logical forms.

Operator table

OperatorMeaningNotes
+ - * / %ArithmeticSafe by default · ! suffix to opt out
^Power / exponentiationx^2, 2^n, 10^k · ^ reads as superscript in math
!Logical NOT (unary)!valid, !empty
& | ??Guard · fallback · null-coalesceShort-circuit · see Conditionals
~&Bitwise ANDflags ~& mask
~|Bitwise ORa ~| b
~^Bitwise XORa ~^ b · ~ prefix = bitwise domain
~!Bitwise NOT (unary)~!value
~< ~>Left / right shiftx ~< 3 · x ~> 1
The ~ prefix means bitwise domain, consistently. Every bitwise operation is visually distinguishable from its logical counterpart. & is guard; ~& is AND-bits. | is fallback; ~| is OR-bits. ! is logical NOT; ~! is bitwise NOT. ^ is power; ~^ is XOR. The ~ prefix is a one-character annotation that shifts the entire symbol into the bitwise domain.
On ^ as power: C programmers used to ^ meaning XOR will need to adjust. In U, ^ means power and XOR requires ~^. The design is internally consistent — since all bitwise operators carry the ~ prefix, there is no bare bitwise ^ to confuse with. The gain: x^2, 2^n, and 10^k read as they would in a math textbook.
Uoperators.u
// Arithmetic — safe by default
total = big + small        // overflow-checked
ratio = num / divisor      // zero-checked
item  = arr[index]         // bounds-checked
total = big +! small       // ! = opt out of overflow check
item  = arr[index!]        // ! = opt out of bounds check

// Power — ^ is exponentiation
area  = side ^ 2           // x squared
scale = 2 ^ bits           // 2^n for bit counts
mag   = 10 ^ exponent      // order of magnitude

// Bitwise — all use ~ prefix
masked  = flags ~& 0xFF    // AND bits
merged  = a ~| b            // OR bits
toggled = a ~^ b            // XOR bits
flipped = ~!value           // NOT bits (unary)
shifted = bits ~< 3          // left shift
aligned = bits ~> 2          // right shift

// Mix — ~ prefix makes bitwise vs logical unambiguous at a glance
valid = (flags ~& MASK) & notExpired  // ~& bitwise, & guard
19Memory

Memory Model — Tiered Allocation

The modifier system gives the allocator perfect information at compile time. Every allocation strategy is determined from the type annotation. No runtime profiling, no GC, no stop-the-world pauses.

ModifierWhere allocatedARCFreed when
-RStack framenoneScope exit — O(1) stack pointer move
+R refcount=1Per-fiber nursery (bump ptr)integerNursery reset — O(1) if never shared
+R refcount>1Main heap (size classes)integerRefcount reaches 0 — deterministic
+R -MImmutable poolintegerRefcount → 0, may be hash-consed
+R (cycles)Heap, candidate listintegerBackground cycle detector — bounded pause
+VGPU device memorynoneLast +V ref drops — DMA dealloc
ARC without atomics: on a single-threaded fiber scheduler, refcount is a plain integer — no atomic_fetch_add, no memory fences, no cache coherence traffic. 10–50× cheaper than Swift or Objective-C ARC.
Genesis nursery: a newly created +R object has refcount=1 — it's "genesis" state, effectively still local. The per-fiber nursery allocates with a bump pointer (O(1)). Only when refcount goes above 1 does the object promote to the main heap. Most objects never promote.
20Memory

Copy — .c()

.c() is the only way to create a copy. No implicit copies anywhere. The compiler has a static copy budget per function — the exact number of allocations it can cause.

What .c() copies — determined by the class definition

.c() is a shallow copy by property declaration. Whether a property is duplicated or shared depends on its modifier, declared on the class:

The class definition is the copy contract. No separate clone() method or copy protocol. The programmer controls copy depth declaratively by choosing -R vs +R per property.

Ucopy.u
// Copy vocabulary — only these four forms exist
ref = obj            // retain: refcount++ only, zero allocation
ref = obj.c()        // copy: -R props by value, +R props share ref
ref = obj.c(+V)     // domain copy: DMA to GPU memory
ref = obj.c(+R)     // promote: -R stack value → +R heap allocation

// COW: .c() is lazy for -R +M properties — no allocation until write
snap = state.c()           // marks COW intent, no allocation yet
readFrom(snap)             // reads shared data, zero cost
snap.field = newVal        // NOW allocates + copies, then writes

// Copy elision: refcount=1 at .c() call site → zero cost
cfg   = loadConfig()       // +R, refcount=1 (genesis)
local = cfg.c()            // refcount was 1 → compiler elides entirely
Prevents at compile time: surprise allocation. There are no implicit copies anywhere in U. The compiler has a static copy budget per function — the exact number of .c() calls determines it.
21Memory

Zero-Copy Returns

Returning a -R value via r.field = ... writes directly into the caller's pre-allocated stack slot. The callee's frame "popping" doesn't copy anything — it just moves the stack pointer. This is Named Return Value Optimization, but guaranteed by the type system rather than hoped for from the optimizer.

Traditional — copies on return
// Callee allocates, caller receives copy
Result makeResult() {
    Result r;          // callee's stack
    r.val = compute();
    return r;          // COPY to caller
}
U — destination-passing, no copy
// Callee writes into caller's slot
f makeResult(): Result
    r.val = compute()  // writes to caller's frame
                        // no copy. Ever.

For +R return types, the heap object is allocated once during construction. The return value is an 8-byte pointer. No copy of the object contents occurs at any point.

22Reference

Defaults — Safe and Fast Require No Annotations

Every default is chosen to be cheapest and safest. You pay for costs by annotating +.

ContextDefaultMeaning
Function parameters-R -M -NLocal, immutable, non-null — zero cost
Local variables-R +M -NLocal, mutable (reassignable), non-null
Class instances-R +M -NLocal mutable — can be promoted to +R with .c(+R)
Return types-R +M -NDPS — zero copy guaranteed
Closures-RStack-allocated, doesn't escape scope
Fiber frame values-ANOT saved at suspension unless annotated +A
Integer literalsI64-bit signed
Decimal literalsN64-bit float
Uzero_annotation_guarantees.u
// A function with zero annotations
f stats(data: [N]): Stats
    total = data.x((acc, val) => acc + val, 0.0)
    mean  = total / data.length
    r => { total, mean }

// What you get for free, by default:
//  ✅ No null dereference    (-N default)
//  ✅ No heap allocation     (-R default)
//  ✅ No ARC cost            (-R: no refcount)
//  ✅ No race condition      (-R: no sharing)
//  ✅ No dangling reference  (-R: scope lifetime)
//  ✅ No iterator invalidation  (.x(): safe)
//  ✅ Zero-copy return       (r.field = DPS)
//  ✅ No async coloring      (fibers)
14.5Concurrency

+A as a Pending Type

The +A modifier marks the async domain — values whose lifetime spans fiber suspension boundaries. This has two consequences from the same rule. Both are instances of lazy materialization.

Two Consequences, One Rule

PositionMeaningExample
state: Req +A
local variable
Value crosses suspension boundary from inside — save to fiber frame at yield state: Req +A = buildReq()
pending: S +A
pending type
Value crosses suspension boundary from outside — a fiber computing this result pending: S +A = a fetch(url)
a f fetch()
declaration
This function exists in the async domain — may suspend a f lookup(key: S): Config
Two consequences, one rule: +A marks values that participate in the async domain. A local variable that must survive suspension, and a pending computation waiting to resolve — both are values that cross fiber boundaries. One modifier. Two consequences. Zero ambiguity.

The Two Valid Call Forms — and Nothing Else

Uthe_two_forms.u
a f lookup(key: S): Config+N   // declared async — may suspend

// FORM 1: Auto-await (default, no annotation needed)
cfg = lookup(key)                   // get Config — transparent suspension if needed
// If lookup doesn't suspend: returns Config directly. One branch check.
// If lookup suspends: fiber pauses, resumes when done, cfg gets Config.
// Call site code: IDENTICAL either way. Zero annotation required.

// FORM 2: Async opt-in (explicit, requires +A on left)
pending: Config+N+A = a lookup(key)  // get pending Config
// New fiber starts. Caller continues immediately. pending is a pending Config.
// 'a' and +A are co-required — compiler enforces both or neither.

The Co-Requirement — Compiler Enforces Both

Utype_errors.u
// TYPE ERROR — 'a' requires +A on LHS
cfg = a lookup(key)            // ❌ 'a' produces Config +A but LHS expects Config
                               //    "remove 'a' — auto-await does this already"

// TYPE ERROR — +A requires 'a' at call site
pending: Config+A = lookup(key) // ❌ LHS is Config +A but call site has no 'a'
                               //    "add 'a' to opt into async domain"

// The error messages write themselves from the rule.
// You cannot have one without the other.

Using a Pending Value

Uusing_pending.u
pending: Config+N+A = a lookup(key)

// Auto-resolve: LHS expects Config — triggers resolution
cfg: Config+N = pending               // fiber suspends if still pending

// Copy: stays pending
other: Config+N+A = pending           // no resolution, stays +A

// Chain: schedule work on resolution — returns S +A
rendered: S+A = pending.x(cfg => render(cfg))

// Multiple subscribers — event emitter pattern falls out naturally
pending.x(cfg => log(cfg))
pending.x(cfg => cache(cfg))
pending.x(cfg => notify(cfg))   // all three run when pending resolves
// No EventEmitter class. No .on() method. Just .x().
Prevents at compile time: async violations. result: S = a fetch(url) (a without +A) and pending: S +A = fetch(url) (+A without a) are both type errors. Auto-await in a non-a f function is also a type error.
14.6Core Principle

Lazy Materialization — Zero Cost by Default

U has two cost mechanisms: .c() copying and +A async. Both follow the same rule. Both are zero-cost in the common case. Both provide caller stability without recompilation.

Lazy Materialization Multiplicity >1 participant needs result Declaration .c() or +A opted in cost paid absent → zero cost absent → zero cost
The Two-Condition Rule: An annotated operation costs nothing unless both hold simultaneously:
1. Multiplicity — more than one participant needs what the operation produces
2. Declaration — programmer explicitly opted in (.c(), T +A, or a call)
When either is absent, the operation is a no-op at runtime. Present in source. Absent in execution.

.c() — Zero Cost at Genesis

// Condition 1 absent: refcount = 1 (genesis)
cfg    = loadConfig()     // refcount=1
local  = cfg.c()          // .c() declared...
// ...but refcount=1 → same pointer returned
// Zero allocation. Zero memcpy.
// .c() in source, absent at runtime.
// Both conditions met: refcount > 1 + mutation
shared = cfg.c(+R)        // refcount now 2
shared.update(newValue)   // mutation follows
// NOW the copy actually happens.
// Cost paid only when genuinely needed.

+A — Zero Cost on Inline Completion

// Condition 1 absent: callee doesn't suspend
// (cache hit, sync path, computed value)
pending: Config+A = a lookup(key)
// fiber runs inline to completion
// one branch check: "suspend?" → no
// returns Config directly. Zero fiber start.
// Both conditions met: callee suspends + fiber waiting
pending: Config+A = a lookup(key)
// actual network call inside lookup
// new fiber starts, caller continues
// Cost paid: fiber start + pending alloc

Caller Stability — Zero Recompilation

When a function gains suspension or sharing, no call site needs to change. This is the practical payoff of lazy materialization.

Ucaller_stability.u — three phases, zero source changes at call sites
// ─── PHASE 1: everything sync ─────────────────────────────────
f lookup(key: S): Config     // sync

f handleReq(key: S): S
    cfg = lookup(key)         // [A] CALL → Config immediately
    r => render(cfg)

// ─── PHASE 2: lookup becomes async ────────────────────────────
a f lookup(key: S): Config    // added 'a' — ONLY CHANGE

// handleReq source is IDENTICAL — zero recompile
f handleReq(key: S): S
    cfg = lookup(key)         // [A] same CALL instruction
                              //     fiber suspends if lookup suspends
                              //     cfg gets Config — caller unchanged
    r => render(cfg)

// ─── PHASE 3: a different caller opts into concurrency ────────
f handleBatch(keys: [S]): [S]
    // Explicit async: fan out all lookups concurrently
    jobs: [Config+A] = keys.x(k => a lookup(k))

    // Auto-resolve: [Config +A] used as [Config] triggers resolution
    cfgs: [Config] = jobs

    r => cfgs.x(cfg => render(cfg))

// Phase 3 also stable: if lookup later becomes sync again,
// 'a lookup(k)' produces trivially-resolved Config +A. Still correct.

The Forward Declaration

pending: T +A = a f() when f is currently sync is a forward declaration of intent:
"If this function later suspends, treat the result as pending."

Today: new fiber, instant-resolved, near-zero cost.
When f later becomes a f: same source, same call site, the result is now a pending computation.
The code using the result does not change in either case.

Summary Table

OperationZero-cost conditionCost paid whenCaller changes?
.c()refcount = 1refcount > 1 AND mutationNever
auto-await result = f()callee completes inlinecallee suspendsNever
opt-in pending: T+A = a f()callee sync (instant resolve)callee actually suspendsNever
T +A forward declcallee currently synccallee later becomes asyncNever — that's the point
14.7Concurrency

Automatic Backpressure in .x() Pipelines

When a .x() lambda returns T +A (via a at the call site), the type signals to the scheduler that this pipeline stage is concurrent. Backpressure is automatic — no semaphore, no library, no configuration.

Ubackpressure.u
// Sequential — inline to tight loop by compiler
items.x((item) => process(item))

// Async method reference — a before the ref = async dispatch
items.x(a Cool.processItem)             // backpressure automatic

// Async lambda with block body
items.x(a (item) => (
    result = a fetch(item)              // suspends here if slow
    a db.write(result)                  // suspends again if needed
    log(result)
))
// fiber suspends at each 'a' call — next item waits — that IS backpressure
// no semaphore, no channel, no configuration needed

// Concurrent pool — a before the outer handler
items.x(a (item) => a fetch(item))     // [T +A] — bounded pool
// scheduler fills pool up to limit
// pool full: new items wait — backpressure
// slot frees: next item starts

// Await the whole batch
results: [S] = a items.x(item => a fetch(item))
// outer 'a': don't return until all items resolved

// Pipeline: stages compose naturally
urls
  .x(url    => a fetch(url))    // [S +A] — concurrent, backpressure applied
  .x(result => parse(result))    // [S] — sequential (no 'a' = auto-await per item)
  .x(parsed => store(parsed))    // [void] — sequential sink

// items can be: array, generator, stream, another .x() chain
// backpressure applies uniformly — source type doesn't matter

Default algorithm and middleware override

The default backpressure for async .x() is sequential — one item at a time. The handler suspends at each inner a call; the next item waits until the current fiber completes. Zero configuration needed.

Parallelism is opt-in via a middleware policy prepended to the handler chain. A policy handler returns none (admit), true (drop/stop), or suspends until capacity allows — same three-signal convention as all .x() handlers:

Ubackpressure policies
// Default: sequential, automatic backpressure
items.x(a processItem)

// Opt-in parallel pool — admit up to 10 concurrent
items.x(Concurrency.pool(10), a processItem)

// Drop when full
items.x(Concurrency.dropLatest, a processItem)

// Custom policy — same interface as any handler
myPolicy = (item) => rateLimit.check(item) ?? r => true | r => none
items.x(myPolicy, a processItem)
LanguageConcurrent iterationBackpressureHow
U.x(item => a f(item))automatictype system: +A return triggers bounded pool
JavaScriptPromise.all(items.map(f))none — all start at onceOOM if items is large
JavaScript (library)pLimit(10)(f)manual semaphoreseparate library, no type safety
Gogoroutines + WaitGroupchannel buffermanual chan + wg.Add/Done/Wait
SwiftTaskGroup + addTaskstructured scopeverbose, requires async context
Ruststream::bufferedexplicit sizeStream adaptor, manual buffer
15Concurrency

The Fiber Scheduler

Three Mechanisms That Cause a Fiber to Yield

MechanismSyntaxWhenRequires a f?
Auto-await resolutionresult = pendingpending not yet resolvedno — automatic
Cooperative yielda;explicitly, let others runyes
Timed yielda n (integer)at least n ms (lower bound)yes
a; and a n are suspension points. Any function containing them must be declared a f. Callers then choose: auto-await (inherit the pause) or opt-in with a f() to run it on a new fiber and avoid the pause. The same co-requirement applies — timed yields are just another reason to mark a function a f.
Bounded execution corollary: A function NOT declared a f contains no suspension points. It runs to completion before yielding — provably synchronous, deterministic, bounded. The compiler enforces this: any a expression inside a non-a f function is a type error. No other language can state this guarantee statically.
Ucooperative_yield.u
// CPU-intensive loop — sprinkle a; to stay cooperative
a f processLargeDataset(data: [Item])
    data.x((item, index) =>
        heavyCompute(item)
        idx % 1000 == 0 & a;    // yield every 1000 items
    )

// Timed yield — be nice for at least 5ms
a f pollingLoop()
    w+I
        result = checkForUpdates()
        result & processUpdate(result)
        a 5                         // yield at least 5ms between polls

// a n is a lower bound — scheduler may not wake you in exactly n ms
// Only hurts your fiber and callers — cannot harm other fibers
// Callers defend with: job: T +A = a pollingLoop() — runs on own fiber

The Event Loop — No Macrotask/Microtask Split

JavaScript — two queues, starvation possible
// Macrotask queue: ONE per tick
// setTimeout, setInterval, I/O callbacks
// Microtask queue: ALL drained per tick
// Promise.then, await continuations
// Microtasks can starve I/O callbacks:
async function spin() {
  while (true) {
    await Promise.resolve()  // microtask
    // I/O callbacks NEVER run
  }
}
U — one scheduler, no starvation
// One run queue. All fibers equal.
// No macrotask/microtask distinction.
// a; yields fairly — fiber goes to
// back of queue, others run first
a f spin()
    w+I
        a;   // fair yield — I/O fibers run

Declarative Toposort — Dependencies from Types

The dependency graph is embedded in the +A types. The scheduler executes in topological order automatically — no manual orchestration.

Udeclarative_toposort.u
// Independent computations — scheduler runs concurrently
cfg:    Config+A = a loadConfig()
schema: Schema+A = a loadSchema()   // independent of cfg

// Dependent — scheduler waits for both before running render
page:   S+A      = a render(cfg, schema)

// The scheduler reads the type graph:
// page depends on cfg and schema
// cfg and schema are independent
// → run loadConfig + loadSchema concurrently
// → run render when both resolve
// Zero manual dependency declaration.
// The dependency graph IS the program.

Pub/Sub from the Modifier System

Multiple .x() calls on the same T +A value register subscribers that all run on resolution. This is Node.js EventEmitter — derived from the type system, not a separate API.

Node.js EventEmitter — runtime, untyped, manual
const emitter = new EventEmitter();
emitter.on('data', cfg => render(cfg));
emitter.on('data', cfg => cache(cfg));
emitter.on('data', cfg => log(cfg));
emitter.emit('data', loadedCfg);
// untyped, memory leak risk
// no backpressure, no topology
U — typed, automatic, zero API
data: Config+A = a loadConfig()
data.x(cfg => render(cfg))
data.x(cfg => cache(cfg))
data.x(cfg => log(cfg))
// type-safe (Config, not any)
// backpressure via .x() pool
// topological order automatic
PropertyU T +ANode EventEmitter
Type safetyfull (T is the event type)none (any)
Dependency trackingcompile-time from typesmanual
Multiple subscribersmultiple .x() calls.on() API
Backpressureautomatic pool limitnone
Memory leak riskno (fiber lifecycle)yes (forgotten listeners)
Ordering guaranteetopological from type graphemission order

Modifiers on Functions — The Clean Rule

No modifiers on function declarations. Modifiers describe values. On a function declaration a f fetch(url: S): S +N:
a is a keyword (like f or d), not a modifier
+N is a modifier on the return value
— parameter modifiers describe the parameter values

Modifiers on function types as values are fine: F() +R (escaping closure), F() +N (nullable function reference). These describe the reference, not the function's behavior.
24Safety

Safety by Construction — Compile Errors

Every major error category below is not caught at runtime, not detected by a linter, not documented in a style guide. Each is a compile-time type error. The program does not build.

Non-Boolean Condition

The condition in ?! and the left side of & must be type L. Integers, strings, and nullable values cannot be used directly as conditions. Convert explicitly with L(expr) or !!(expr).

// ❌ integer as condition
5 ? 2 ! 8           // ERROR: 5 is I, not B
a = 5 ? 2 ! 8       // ERROR: assignment returns I
count & process()  // ERROR: count is I
user & show()      // ERROR: user is User +N

// ✅ explicit condition
a == 5 ? 2 ! 8      // OK: == returns B
count > 0 & process() // OK
user != none & show() // OK
B(count) ? "y" ! "n"  // OK: cast
!!(name) ? "y" ! "n"  // OK: double-not

// | and ?? are exempt — value selectors
label = name | "default" // OK: S, not B

Use-After-Free / Scope Escape

A -R (stack) variable has no refcount and cannot outlive its scope. Assigning it to a +R slot or returning it as +R is a type error.

// ❌ stack var assigned to heap slot
local: Vec2 = Vec2{1.0, 2.0}  // -R
ref: Vec2 +R = local           // ERROR: -R → +R

// ❌ returning stack var as heap ref
f makePoint(): Vec2 +R
    p: Vec2 = Vec2{1.0, 2.0}  // -R
    r => p                     // ERROR: -R escapes scope

// ✅ explicitly promote to heap
    r => pt.c(+R)               // OK: copies to heap first

Null Dereference

-N is the default. Any use of a +N value without a null guard is a type error. The compiler tracks nullability through every binding.

// ❌ accessing +N without check
name: S +N = query()
len = name.length()      // ERROR: name is +N

// ✅ guard before use
name != none & len = name.length()
name?.length()         // OK: optional chain
display = name ?? "—"  // OK: null coalesce

// ❌ returning +N where -N expected
f getUser(): User
    r => db.find(id)   // ERROR: db.find returns User +N
f getUser(): User +N   // ✅ declare it nullable

Data Race

+R +M without a +P policy is a type error. Every shared mutable object must declare how concurrent writes are mediated.

// ❌ shared mutable without policy
state: Config +R +M = Config()
// ERROR: +R +M requires +P policy

// ✅ declare the policy
state: Config +R +M +M(MVCC) = Config()
state: Config +R +M +M(Actor) = Config()

// ❌ two +M refs to same object
a: Vec2 +R +M = Vec2()
b: Vec2 +R +M = a      // ERROR: +M alias without policy

// ✅ policy handles concurrent access
a: Ctr +R +M +M(MVCC) = Ctr()
b: Ctr +R = a          // OK: b is +R -M (read only)

Async Violations

a at a call site and +A on the LHS are co-required. Auto-await in a non-a f function is also a type error.

// ❌ a without +A on LHS
result: S    = a fetch(url)  // ERROR: a needs +A
// ❌ +A without a at call site
pending: S +A = fetch(url)   // ERROR: +A needs a

// ✅ both together
pending: S +A = a fetch(url) // OK

// ❌ auto-await in non-a f function
f sync()
    result = fetch(url)      // ERROR: fetch is a f,
                             // sync() can't suspend
// ✅ declare the function suspendable
a f sync()                   // or: use a fetch() for new fiber

Const Property Mutation

-M on a property is a const declaration. No amount of +M on the enclosing binding overrides it. Mutation requires +M at every step of the access chain.

// ❌ mutating -M property
d Config
    host: S       // -M (default) — const
    port: I +M    // explicitly mutable

cfg: Config +R +M = Config()
cfg.host = "x"   // ERROR: host is -M
                 // binding's +M does not override

cfg.port = 8080  // ✅ port is +M

// ❌ writing through immutable binding
pt: Vec2 = Vec2() // -M binding (default)
x.y = 1.0        // ERROR: x is -M

Escaping Closure Capturing -R

An escaping closure (+F +R) may outlive the current scope. It cannot capture -R stack variables — those will be gone when the closure runs.

// ❌ escaping closure captures stack var
f setup()
    msg: S = "hello"          // -R stack
    cb: (->S) +F +R =
        () => console.log(msg) // ERROR: cb is +F +R,
                               // msg is -R — will escape

// ✅ non-escaping: must be called within scope
    cb: (->S) +F -R =
        () => console.log(msg) // OK: -R closure, in-scope only

// ✅ or promote msg to heap
    msg: S +R = "hello".c(+R)
    cb: (->S) +F +R = () => console.log(msg)

Injection Attack

Functions expecting Html or Sql types reject raw S strings. The only way to produce Html is via html`...` which escapes all interpolations at compile time.

// ❌ raw string where Html expected
f render(page: Html)
    ...

content: S = "<h1>" + input + "</h1>"
render(content)  // ERROR: S is not Html

// ✅ html template: input is escaped
content = html`<h1>{input}</h1>` // type: Html
render(content)  // OK

// ❌ dynamic template tag
tag = html
page = tag`{input}` // ERROR: tag must be literal

Implicit Copy / Surprise Allocation

There are no implicit copies. Assignment retains (refcount++). Only .c() copies. The compiler has a static copy budget per function.

// ❌ no hidden copies anywhere
a: [I] +R = [1, 2, 3]
b = a           // retain: refcount++, no copy
b.append(4)     // ERROR: b is not +M
                // (and mutation would affect a)

// ✅ explicit copy when you need independence
b = a.c()       // explicit copy, now independent
b +M .append(4) // OK: b is its own array

// ❌ function with unknown copy count
// Every .c() is visible — static copy budget
// is proven at compile time

Summary — Error Categories Eliminated

ErrorHow U prevents itLanguages where it's runtime
Unhandled error typeDeclared ! ErrType errors checked for exhaustive handling at call siteAll languages (runtime)
Non-boolean condition?! condition and & require L; 5 ? 2 ! 8 is a type errorC, JS, Python, Ruby
Use-after-free-R cannot be assigned to +R — type errorC, C++, Zig (manual)
Null dereference-N default — +N access without guard is type errorC, Java, JS, Go, Swift (partial)
Data race+R +M defaults to +M(MVCC) — explicit unsafe sharing impossibleGo (race detector), C, C++
Async cascade / coloringa/+A co-requirement — mismatch is type errorJS, Swift, Python, Rust async
Use-after-await+A annotation on locals — missing it is type errorJS (closure capture bugs)
Const mutation-M property — assignment is type error regardless of bindingC++ (UB), Java (partial), JS
Closure scope escape+F +R cannot capture -R — type errorC++ (UB), Swift (partial)
XSS injectionRaw SHtml — type error at function boundaryAll languages without template types
SQL injectionRaw SSql — type error at function boundaryAll languages without typed queries
Surprise allocationNo implicit copies — static copy budget proven per functionC++, Rust (some cases), Swift
i18n missing keysmsg`{key}` — missing locale key is compile errorAll languages (runtime crash)
17.5Memory

Access Policies — +P()

Every +R +M binding requires a +M(Policy) declaration. Write policies go on +M (+M(Actor), +M(MVCC), etc.) and govern how concurrent writes are mediated. Lifetime/cleanup policies go on +R (+R(RAII)) and fire when the last reference drops. GC is one policy among many — and not the default. The default for +R is ARC without atomics (single-threaded fiber scheduler means no cross-thread refcount races).

PolicySemanticsCleanupBest for
+R(RAII)drop() fires when last ref drops — deterministicImmediate, ref-countedFile handles, GPU resources, DB connections
+M(MVCC) DEFAULTOptimistic: readers never block, writers get a new version; conflicts retryOld versions GC'd when no readers hold themConfig, shared state with rare writes
+M(CRDT)Commutative merge: all writes eventually consistent, no conflictsRefcountedDistributed counters, sets, logs
+M(Actor)Serialized message queue: all access goes through the actor's inboxRefcountedMutable state with concurrent writers, UI components
+M(Mutex)Exclusive lock: one writer at a time, readers blockRefcountedLow-level critical sections
+R(Network)Remote proxy: reads/writes are serialized RPC callsRemote lifetimeMicroservices, distributed objects
Upolicies.u
// RAII: drop() fires deterministically on last ref
d DbConn+R(RAII)
    handle: I
    f drop(conn: DbConn+R) => db.close(conn.handle)

f query(sql: S): S
    conn: DbConn+R = DbConn.open()   // refcount=1
    result = conn.exec(sql)
    r => result    // conn refcount→0: drop() fires, connection closed
    // No finally, no try-with-resources, no defer

// MVCC: readers never block, writers get a new version
config: AppConfig+R+M(MVCC) = AppConfig.load()
config.reload()   // new version; existing readers see old version until done

// Actor: all access serialized through message queue
d Counter+M(Actor)
    count: I+M = 0
    f increment() => t.count = t.count + 1
    f get(): I   => r => t.count
// Concurrent callers are queued — no data race, no mutex annotation

Comparison: how other languages manage state lifetime

LanguageDefaultDeterministic cleanup?Shared mutableNotes
JavaGC (non-deterministic)No — finalize() deprecatedsynchronized, volatiletry-with-resources for manual RAII. GC pauses unpredictable.
C++Manual + RAII patternYes — destructorsstd::mutex, std::atomicshared_ptr = ARC but with atomic refcount overhead. Easy to misuse raw pointers.
GoGCNo — use deferChannels, sync.Mutexdefer is explicit and forgettable. No deterministic cleanup.
RustOwnership + DropYes — ownership enforces itArc<Mutex<T>>Deterministic, zero-overhead. Borrow checker annotation burden. No MVCC/CRDT out of box.
JavaScriptGCNonone — single threadNo deterministic cleanup. WeakRef / FinalizationRegistry best-effort only.
U-R stack (free), +R ARC without atomicsYes — +R(RAII) on last ref drop+M(Actor|MVCC|CRDT|Mutex)Policy declared at binding site. GC available as a policy but not the default. Single-threaded fiber scheduler means ARC needs no atomic operations.
Default is MVCC. Optimize down. +R +M without an explicit write policy is +M(MVCC). Readers never block. No deadlocks. Write conflicts retry automatically. When you have a measured reason to trade correctness for throughput: +M(Actor) for serialized writes, +M(Mutex) for raw lock performance, +M(CRDT) for eventual consistency. The same direction as Rust's safety model — safe by default, opt into unsafe when you need to.
GC as a policy, not a mandate. Garbage collection makes a specific tradeoff: determinism and latency for programmer convenience. U treats it as one option. The default for stack values (-R) is zero cost — they disappear at scope exit. The default for heap values (+R) is ARC — deterministic, immediate cleanup, no GC pauses. For cases where you genuinely want GC semantics (long-lived object graphs with complex reachability), +R(GC) is available. You opt into the tradeoff explicitly rather than having it imposed on everything.
17.6Concurrency

MVCC — Default Write Policy

+R +M without an explicit write policy defaults to +M(MVCC). You never write it — it's just what happens. Override to a cheaper policy when you have profiled a reason.

Semantics
ReadersNever block. Each reader sees a consistent snapshot at the moment it starts reading.
WritersOptimistic. Write completes; if another fiber modified the same object between your read and write, your write retries automatically.
Atomicity unitThe +R +M(MVCC) object plus all its inline (-R) properties and inline maps. They change as one version. +R properties inside have their own separate versioning.
Cross-object transactionsNot in v1. If A and B must change together, make them fields of one object.
Umvcc.u
// +R +M defaults to MVCC — no annotation needed
config: Config+R+M = Config.load()

d Config
    host:  S            // inline -R — atomic with Config
    port:  I+M         // inline -R — atomic with Config
    flags: {S: L}       // inline map — atomic with Config
    cache: Cache+R(RAII)  // separate heap object — its own versioning

// Writing host + port + flags atomically — one version, one commit
config.host  = "newhost"
config.port  = 9090
config.flags["debug"] = true
// All three committed as one unit — other fibers see old or new, never partial

The << patch operator

The atomic write primitive. obj << { field: newVal } is one MVCC version swap — recursively patches inline (-R) fields, stops at +R boundaries (those are separate objects). For large arrays and maps, the runtime uses path-copying persistent data structures: O(log n) writes, structural sharing, disjoint patches never conflict.

Upatch.u
// Atomic multi-field patch — one MVCC version swap
config << { host: "newhost", port: 9090 }   // atomic, retries on conflict

// Nested inline — recursively patches -R fields
server << { address: { host: "new" }, port: 443 }   // address.scheme unchanged

// Array element patch — O(log n), structural sharing
items << { [500]: updatedItem }   // path-copies only the path to index 500

// Two concurrent fibers — no conflict if different indices
// Fiber A: items << { [500]: valA }   ✅ different subtrees
// Fiber B: items << { [700]: valB }   ✅ both commit

// On -R local: same syntax, no MVCC overhead (compiler knows)
localConfig << { port: 9090 }   // plain field update, -R = no versioning

Exception handling and rollback

The fiber's uncommitted write-set is rolled back or committed based on how its exception handler responds. No begin/commit/rollback keywords.

e.x() handler returnsWrite-setFiber
trueCommitted — handler affirmed the writes are validContinues
none or falseRolled back — silence means unhandled, fail closedException propagates
Uncaught exceptionAlways rolled backFiber exits
Umvcc-exceptions.u
e.x((err: ValidationError) => (
    log(err.msg)
    r => true    // handled — writes committed, fiber continues
))
e.x((err: CorruptStateError) => (
    alert(err.msg)
    // returns none — unhandled, writes rolled back, propagates up
))

// Fail closed: forgetting to return true = rollback, not silent commit
// You must consciously affirm the write-set is valid
Optimize when you need to. Override the default when profiling shows it matters: +M(Actor) for serialized writes with lower memory overhead, +M(Mutex) for raw lock performance, +M(CRDT) for eventual consistency. The default protects you; the overrides let you trade correctness guarantees for throughput.
23.5Comparisons

Exceptions, RAII & Null — U vs C++, Java, Rust, Go

Exception handling

LanguageMechanismTyped?ExhaustivenessHappy path noise
C++try/catch — throw anything, even intsNononeHigh — try/catch blocks everywhere
JavaChecked + unchecked. throws Exception on everythingPartiallyChecked only — widely bypassedHigh — try/catch/finally verbosity
Pythontry/except/finally — any object throwableNononeHigh
GoReturn (T, error) — no exceptionsYesIgnored silently if you forget to checkHigh — if err != nil everywhere
RustResult<T,E> + ? operator — no exceptionsYesCompiler enforces handlingMedium — ? at every fallible call
JavaScripttry/catch/finally — untyped, Promise.catch asyncNononeHigh + async split
Ue.x() typed handlers, object interfaces, library policiesYesCompiler checks ! Type vs e.x() registrationsZero — handlers declared once, happy path clean
The Java lesson: Checked exceptions were the right idea — declare what you throw, check at call sites. The problem was verbosity: you had to list every subtype at every frame. Java programmers responded by writing throws Exception everywhere, defeating the purpose. U solves this with inheritance: ! NetworkError covers TimeoutError : NetworkError and every future subtype. One declaration covers the whole family.

Policy arrays vs try/catch — side by side

The same error handling scenario in Java, C++, and U. The Java and C++ versions are not pathological — they are idiomatic. The noise is intrinsic to the mechanism.

Javafetch-and-process
// Every caller must list all thrown types
// or add throws Exception — defeating the purpose
public Response fetch(String url)
    throws NetworkException,
           TimeoutException,
           SSLException {
    // ...
}

try {
    var result = fetch(url);  // happy path
    process(result);          // buried under catch blocks
} catch (NetworkException e) {
    logger.warn(e.getMessage());
    retry();
} catch (TimeoutException e) {
    backoff(e.getDelay());
} catch (SSLException e) {
    alertSecurity(e);
} finally {
    cleanup();
}
// Reuse? Copy-paste. No policy objects.
C++fetch-and-process
// Untyped — anything throwable
// No exhaustiveness checking
Response fetch(std::string url);
// (what does it throw? read the docs, maybe)

try {
    auto result = fetch(url);
    process(result);
} catch (const NetworkError& err) {
    logger.warn(err.what());
    retry();
} catch (const TimeoutError& err) {
    backoff(err.elapsed);
} catch (...) {
    // silently swallows anything else
}
// Stack unwinding fires on every throw
// Reuse? Inline only. No composable policies.
Ufetch-and-process — policy arrays
// Policies are typed arrays — defined once, reused everywhere
networkPolicies: [(NetworkError)->()] = [
    logToSentry,         // ordered, defined by library
    notifyOncall,
    retryOnRecoverable
]

// Every call site — one line, happy path clean
a f run(url: S)
    e.x(MyLib.networkPolicies)     // spread array onto chain
    e.x(MyLib.sslPolicies)         // multiple arrays composable
    // compiler: all ! NetworkError ! SSLError covered ✅

    result = a fetch(url)          // happy path reads first, clean
    process(result)

// Inheritance: ! NetworkError covers TimeoutError : NetworkError
// No need to list every subtype — the Java checked-exception trap avoided
// Zero-cost happy path — handler tables built statically, no stack unwinding
What policy arrays give you that Java and C++ cannot:
  • Define handlers once in a library, use at 100 call sites — e.x(MyLib.networkPolicies)
  • Compiler verifies all declared ! Type errors are covered — no missed cases
  • Early-exit chain: one handler returning false stops that type's chain, others unaffected
  • Happy path appears first and reads clean — error handling is a preamble, not a wrapper
  • Inheritance means ! NetworkError covers all subtypes — no Java-style subtype explosion
  • Static dispatch tables — zero cost when no exception is thrown, no stack unwinding on throw

RAII — resource cleanup

LanguageMechanismAutomatic?Notes
C++Destructors on stack objects. Smart pointers (unique_ptr, shared_ptr)YesRAII is a pattern — not enforced. Easy to misuse raw pointers. shared_ptr has atomic refcount overhead.
Java / Python / JSGC — no deterministic cleanup. try-with-resources / with / using as workaroundsNoCleanup is non-deterministic. File handles, DB connections left open until GC decides to run.
RustDrop trait. Ownership ensures exactly-once drop. No GC.YesDeterministic, zero overhead. But borrow checker annotation burden. No shared ownership without Arc<Mutex<T>>.
Godefer — explicit, not automaticNoYou must remember to write defer file.Close(). Forgotten defers = resource leaks.
U+R(RAII) on class — drop() fires when last reference dropsYesARC without atomics (single-threaded fiber scheduler). Deterministic. No borrow checker. Works with shared +R refs — drops when refcount hits zero.
Uraii.u
// +R(RAII): drop() called automatically when last +R ref drops
d FileHandle+R(RAII)
    fd: I
    f drop(t: FileHandle+R)   // compiler calls this on last-ref-drop
        os.close(t.fd)

// No defer, no try-with-resources, no explicit close()
f readConfig(): S
    fh: FileHandle+R = open("/etc/config")  // refcount=1
    data = fh.read()
    r => data    // fh refcount→0 here: drop() fires, fd closed

// vs C++: shared_ptr uses atomic refcount (expensive cross-thread)
// U: ARC is plain integer arithmetic — no atomics (single-threaded fibers)

Null safety

LanguageNull representationNull dereferenceDefault
C / C++Null pointer (nullptr)Undefined behavior — crash or worseNullable by default
Javanull referenceNullPointerException at runtimeNullable by default
JavaScriptnull and undefinedTypeError at runtimeNullable by default
SwiftOptional<T> — must unwrapCompile error (if you use ! force-unwrap: crash)Non-null default
KotlinT? nullable typeCompile errorNon-null default
RustOption<T> — must match/unwrapCompile errorNon-null default
GoNil pointers, nil interfacesRuntime panicNil by default
UT +N — explicit nullable annotationCompile error — +N value requires guard before access-N by default — non-null without annotation
U vs Swift/Kotlin/Rust on null: All four make null safe, but U's approach differs. Swift/Kotlin require explicit unwrapping (if let, ?.let). Rust requires pattern matching. U uses name != none & use(name) or name ?? fallback — the same operators used for every other conditional. No special null-unwrapping syntax to learn.
24Comparisons

Async & Concurrency: U vs Go, JavaScript, Swift+GCD

U vs JavaScript — Structure vs Convention

JS async/await unwinds the entire call stack at each await. Every local variable surviving the suspension is serialized to a heap-allocated Promise closure. This is why async infects callers — the return type changes from T to Promise<T>.

JavaScript — stack unwind per await
// Every await: full stack unwind to event loop
// Local state serialized to heap Promise closure
// async infects every caller in the chain
async function main() {
  await processAll(items)   // infected
}
async function processAll(list) {
  for (const item of list) {
    await fetch(item)       // infected
  }
}
// Two queues: macrotask (1 per tick) +
// microtask (ALL drained per tick)
// Microtasks can STARVE I/O callbacks
U — fiber stack preserved, zero annotation
// Auto-await: fiber stack stays in memory
// No serialization. No heap per await.
// No annotation on main() — no coloring
f main()
    processAll(items)     // plain call

f processAll(list: [Item])
    list.x(item =>
        result = fetch(item) // auto-await
        store(result)
    )
// One scheduler. No queue distinction.
// I/O never starved.
JS microtask vs macrotask explained: The event loop runs ONE macrotask (setTimeout, I/O callback) per tick, then drains ALL microtasks (Promise.then, await continuations). If a microtask adds another microtask, it runs immediately — potentially starving I/O callbacks forever. U has no such distinction: one fiber scheduler, one queue, no starvation possible.
PropertyUJavaScript
Await syntaxnone (auto-await default)await everywhere
Async opt-ina f()T +Areturn Promise, await caller
Heap per awaitno — fiber stack preservedyes — Promise closure
Coloringsolvedpresent
Queue starvationimpossiblemicrotasks starve I/O
Backpressureautomatic (.x(k => a f(k)))library only (p-limit)
Generator yield depthany depth (stackful fibers)top-level only (stackless)

U vs Go — Same Ergonomics, Better Guarantees

Go is the closest language to U's concurrency ergonomics. Goroutines solve coloring — any function can block, no annotation needed. The differences are frame size, the absence of a pending type, and safety.

Go — WaitGroup ceremony, full stack saved
// Fan-out/fan-in requires ceremony
var wg sync.WaitGroup
results := make([]Config, len(keys))
for i, key := range keys {
    wg.Add(1)
    go func(i int, key string) {
        defer wg.Done()
        results[i] = lookup(key) // full stack saved
    }(i, key)           // subtle closure bug risk
}
wg.Wait()
// goroutine saves ENTIRE stack
// no equivalent of +A to minimize
// nil pointers: runtime panics
U — two lines, frame minimization, null safe
// Fan-out/fan-in — two lines
jobs: [Config+A] = keys.x(k => a lookup(k))
cfgs: [Config]    = jobs   // auto-resolve

// Fiber frame = only +A values saved
// (not entire stack)
// -N default: null = compile error
// No WaitGroup. No closure bug.
// No pre-allocated result slice.
PropertyUGo
Coloringsolvedsolved (goroutines)
Frame at suspensiononly +A valuesfull goroutine stack
Pending typeT +Anone (channels/WaitGroup)
Fan-out/fan-in.x(k => a f(k))WaitGroup + goroutines
Null safety-N default (type error)nil panics at runtime
Injection safetytype errornone
GCnone (ARC)GC pauses

U vs Swift + GCD — Annotations vs Structure

Swift evolved from GCD completion handlers, to Combine, to async/await (5.5+). Each model accumulated annotations. U encodes the same constraints structurally.

Swift — annotation proliferation
// Actor: new keyword, new rules
actor Counter {
    var value = 0
    func increment() { value += 1 }
}
await counter.increment()  // infects callers

// @escaping: separate annotation
func on(handler: @escaping () -> Void) { }

// TaskGroup: verbose structured concurrency
await withTaskGroup(of: Config.self) { group in
    for key in keys {
        group.addTask { await lookup(key) }
    }
}
// 'in' keyword: 4 different meanings in Swift
U — same semantics, zero new concepts
// Actor = class with proxy policy
d Counter +M(Actor)
    value: I
    f increment(c: Counter+R+M)
        t.value = t.value + 1
counter.increment()  // no await needed

// @escaping = +R on closure
f on(handler: F()+R) { }

// TaskGroup = .x() with 'a'
cfgs = a keys.x(key => a lookup(key))
// 'in' keyword: zero uses in U
PropertyUSwift + GCD
Coloringsolvedpresent (Swift 5.5+)
Escaping closures+R modifier@escaping annotation
Actor modelproxy policy on classactor keyword + 3 annotations
Concurrent iteration.x(k => a f(k))withTaskGroup { group in ... }
Keyword reusezeroin: 4 different meanings
GCnoneweak/unowned GC cycles
25Comparisons

Ownership & Memory: U vs Rust and Zig

U vs Rust — Modifier Algebra vs Borrow Checker

Rust's borrow checker statically proves no aliased mutation exists. U routes all mutation through a proxy with a declared policy. The outcomes are similar — memory safety, race freedom — but the mechanisms and ergonomics differ significantly.

Rust — borrow checker, single writer
// Shared mutable: Arc>
use std::sync::{Arc, Mutex};
let state = Arc::new(Mutex::new(Config::new()));

// Clone Arc for every thread that needs it
let state2 = Arc::clone(&state);
thread::spawn(move || {
    let mut s = state2.lock().unwrap();
    s.update(delta);
});

// Single &mut: one writer at a time
// No MVCC, no CRDT built in
// Async adds coloring (async fn type)
// Copy elision: optimizer heuristic
U — proxy policy, multiple writers possible
// Shared mutable: +R +M with policy
state: Config+R+M = Config() +M(MVCC)

// MVCC: readers never block
snap = state.c()             // instant snapshot
state.update(delta)          // serialized write

// CRDT: commutative — any order
votes: GCounter+R+M = GCounter() +M(CRDT)
votes.inc(nodeId)            // concurrent safe

// No async coloring
// .c() elision: compiler-guaranteed
Borrow checker vs modifier: Rust proves non-escape via whole-program analysis. U's -R annotation encodes the same fact: the programmer states it, the compiler reads it. No analysis needed.
Arc<Mutex<T>> vs T +R +M: Rust's pattern Arc<Mutex<T>> encodes: heap (Arc), shared (Arc), mutable (Mutex). U writes: T +R +M +M(Mutex). Same semantics, three modifiers from the standard algebra. Change Mutex to MVCC for non-blocking reads. Change to CRDT for commutative writes. Rust has no equivalent mechanism — each requires a different library type.
PropertyURust
Memory safetymodifier algebra (annotation)borrow checker (analysis)
Aliased mutationpermitted via proxy policyforbidden (&mut exclusive)
Multiple writersMVCC, CRDT policiesimpossible in safe Rust
Shared mutableT +R +M +M(X)Arc<Mutex<T>>
Null safety-N default (type error)Option<T> (unwrapping)
Async coloringsolvedpresent (async fn type)
Caller stabilityzero recompile when callee adds asyncrewrite all callers
Copy elisioncompiler-guaranteed (genesis)optimizer heuristic (RVO)
GPU domain+V modifier, first-classunsafe (wgpu, cuda-rs)
Annotation burden3 modifiers + domainslifetimes, Pin, Arc/Rc/RefCell

U vs Zig — Modifier as Allocator, Fiber as Frame

Zig makes allocation explicit via allocator parameters: every allocating function takes an Allocator argument. U encodes the same information in the modifier: -R = stack (no allocator needed), +R = heap (runtime allocator). Same visibility, different mechanism.

Zig — allocator parameter chains
// Every allocating function takes allocator
// Adding allocation deep in chain forces
// adding allocator param to every caller
fn process(ally: Allocator, data: []T) ![]T {
    const result = try ally.alloc(T, data.len);
    defer ally.free(result);
    // ...
}

// Async: stackless frame-based
// coloring present (async fn)
// No +A equivalent — no lazy await
U — modifier on value, not parameter
// Allocation in the type, not the signature
f process(data: [T]): [T]+R
    // +R on return = heap allocated
    // callers see T +R — no param change
    r => transform(data)

// Async: coloring solved
// +A = lazy pending type
// T +A forward decl: caller stable
Allocator coloring in Zig: adding heap allocation deep in a call chain forces adding the Allocator parameter to every intermediate function — a form of the coloring problem for allocators. U's +R modifier is on the value's type, not the function's parameter list. Callers see a different return type (T +R) but need no new parameter.
PropertyUZig
Allocation strategy+R/-R modifier on valueexplicit allocator parameter
Null safety-N default (type error)explicit ?T optional
Async coloringsolvedpresent
Pending typeT +Anone
Error handlinge propagates up fibertry at every call site
Copy semantics.c() explicit, genesis elisionmanual, explicit
GPU+V modifiernone
Injection safetytype error (templates)none
23.5Comparisons

U vs Swift — Explicit Conventions vs Structural Encoding

Swift is the closest language to U's goals: memory safety without GC, null safety, value/reference distinction, and a modern concurrency model. The differences reveal a consistent pattern: Swift uses convention and annotation; U uses structure and type.

@escaping vs +R

Swift requires @escaping on closures that outlive their calling scope — they're heap-allocated and may be called after the function returns. U encodes this as +R on the closure type.

Swift — @escaping annotation
// Must mark @escaping manually
// Forgetting it is a compile error
// No other type information conveyed
func onEvent(handler: @escaping () -> Void) {
    listeners.append(handler)  // heap allocation here
}

// Non-escaping: stack-allocated, cannot outlive call
func transform(fn: (Int) -> Int) -> Int {
    return fn(42)   // fn is -escaping by default
}
U — +R modifier on closure
// +R: heap-allocated, may outlive call
// The same modifier system as everything else
f onEvent(handler: F()+R)
    listeners.append(handler) // heap: +R tells you

// -R: stack-allocated, default
f transform(fn: F(I):I): I
    r => fn(42)  // -R default: compiler knows

Swift's in Keyword — One Word, Four Jobs

Swift reuses in in four distinct syntactic contexts. U has zero context-dependent keyword reuse.

Swift contextSwift syntaxU equivalent
Closure body separator{ (x: Int) in x + 1 }x => x + 1
For-in loopfor item in items { ... }items.x(item => ...)
Collection iterationfor (i, v) in items.enumerated()items.x((item, idx) => ...)
Range loopfor i in 0..<10 { ... }(0..10).x(i => ...)

Swift Actors vs U Proxy Policies

Swift — actors are a separate type
// New keyword, new type, new rules
actor Counter {
    var value = 0
    func increment() { value += 1 }
    // Callers MUST use await — coloring!
}
await counter.increment()

// nonisolated = not actor-protected
// @MainActor = global actor
// @Sendable = can cross actor boundary
// Three new annotations to learn
U — actor is just a proxy policy
// No new keyword — just a class with policy
d Counter +M(Actor)
    value: I
    f increment(self: Counter+R+M)
        t.value = t.value + 1

// Caller: no 'a' needed — fiber handles it
counter.increment()

// Same +R +M pattern as MVCC, CRDT, Mutex
// One concept, four policies, zero new keywords

Swift async/await — Same Coloring Problem

Swift vs Uasync_comparison
// Swift: async infects callers — coloring present
// func fetchData() async throws -> Data { ... }
// let data = try await fetchData()  // caller must be async
// — every function in the chain must be marked async

// U: no coloring — any function can call any function
a fetchData(): S+N   // only fetchData is marked 'a'
f main()               // NOT marked 'a' — no annotation needed
    data = a fetchData() // 'a' is at the call site, not on main()

// Swift: withTaskGroup for concurrent iteration (verbose)
// await withTaskGroup(of: Data.self) { group in
//     for url in urls { group.addTask { await fetch(url) } }
// }

// U: .x() with +A return — automatic, zero ceremony
results = a urls.x(url => fetch(url))  // done

Full Comparison Table — U vs Swift, Rust, Go, JS, Zig, C++

PropertyUSwiftRustGoJSZig
Memorymodifier algebraARC+GCborrow checkerGCGCmanual
Null safety-N default (type err)Optional<T>Option<T>nil panicundefined?T explicit
Data racesimpossible (single thread)actor isolationtype errorrace detectorimpossibleno races
Async coloringsolved — fiberspresentpresentsolvedpresentpresent
Pending typeT +A (built-in)noneFuture<T>nonePromise<T>none
Auto backpressureyes — type-drivennonononono
Escaping closures+R modifier@escapinglifetimeGCGCmanual
Actor modelproxy policyactor keywordlibrarygoroutine+channonenone
Concurrent iter.x() + backpressureTaskGroupStream::bufferedgoroutine+wgPromise.allmanual
Injection safetytype errornonelibrarypartialnonenone
i18n correctnesscompile-timeruntimeruntimeruntimeruntimeruntime
Keyword reusezero (1 meaning each)in: 4 meaningsminimalminimalthis: contextminimal
GPU domain+V modifierMetal onlyunsafe wgpunoneWebGPUnone
GC pausesnone (ARC)weak/unowned GCnoneGCGCnone
Keyword count10 (1 letter each)~80+~50+~25~60+~30
23Reference

U vs Rust, Go, Zig, JavaScript

U occupies a previously unoccupied intersection: Go's concurrency ergonomics, Rust's safety, C's performance, and compile-time injection and i18n safety no existing language provides.

PropertyURustGoZigJavaScript
Memory safetymodifier algebraborrow checkerGCmanual+comptimeGC
Null safety-N default (type error)Option<T>nil panicsexplicit optionalundefined/null
Data racesimpossible (single fiber thread)type errorrace detectorno racesimpossible (single thread)
Async coloringsolved — fiberspresentsolved — goroutinespresentpresent
Async frame sizeminimal (+A oracle)minimal (stackless)full stackminimalheap closure
Multiple writersyes (MVCC, CRDT)no — single &mutmutex onlymutex onlyyes (single thread)
Injection safetytype error (templates)library onlypartialnonenone
i18n correctnesscompile-time (msg``)runtimeruntimeruntimeruntime
GC pausesnone (ARC)noneGC pausesnoneGC pauses
ARC atomicsnone (single thread)Arc: always atomicN/A (GC)N/A (manual)N/A (GC)
All copies explicityes — .c() onlyyes — .clone()implicitmostlyimplicit
GPU first-class+V modifierunsafe (wgpu)nonenoneWebGPU only
MapsBag type built-inHashMap onlymap[string]anymanualnative objects
Keyword count10 (all 1 letter)~50+~25~30~50+

vs Rust — Same Goals, Different Philosophy

Rust prevents aliased mutation through static analysis — the borrow checker proves no two mutable references exist simultaneously. Powerful but the annotation burden is substantial: lifetime annotations, Pin/Unpin, Rc/Arc/RefCell proliferation, sharp &T/&mut T distinction. Rust async has the coloring problem. Stackless coroutines are lean but you cannot yield from nested calls without propagating async upward.
U prevents aliased mutation through architecture: +R +M objects use proxy-mediated writes, meaning multiple concurrent writers are possible via MVCC or CRDT — impossible in safe Rust. The single-threaded fiber scheduler eliminates most synchronization entirely. +A minimizes fiber frames without losing stackful ergonomics. No lifetime annotations. No Pin.

vs Go — Better Concurrency, Better Safety

Go solves coloring with goroutines — any function can block without annotation. But goroutines save the entire call stack at suspension. No equivalent of +A to tell the runtime "only save these two values." Go also has nil pointers (runtime panics, not type errors), GC pauses, and no injection safety at the language level.
U matches Go's ergonomics and adds frame minimization via +A. Null safety is a type error, not a runtime panic. No GC — ARC with O(1) cycle detection. Templates make injection impossible.

vs Zig — Same Performance Goals, More Ergonomics

Zig is excellent: manual memory with explicit allocators, no hidden control flow, comptime for compile-time computation, great C interop. But null safety requires explicit ?T optionals, async has the coloring problem, and there is no template safety or i18n.
U shares Zig's philosophy — no hidden allocations, explicit costs, zero-overhead abstractions. The +R/-R modifier encodes allocation strategy; U's answer to Zig's allocator parameters: the allocation strategy is encoded in the type. U adds null safety, injection safety, and fiber concurrency that Zig doesn't have.

vs JavaScript — Same Ergonomics, Radically Different Guarantees

JS async/await explained: when JS encounters await, the entire call stack must unwind back to the event loop. Every local variable surviving the suspension is serialized into a heap-allocated Promise closure. This is why async infects callers — each frame must opt in to this serialization. Two separate queues: macrotasks (setTimeout, I/O — one per loop tick) and microtasks (Promise.then — all drained before the next macrotask). Microtasks can starve I/O callbacks if they keep adding more microtasks. Generators are stackless and can only yield at the top level — not from nested calls.
U fibers: save the stack pointer (~100 bytes of registers). The stack stays in memory unchanged. No heap allocation per suspension, no stack serialization, no unwind. No macrotask/microtask distinction — the scheduler just picks the next ready fiber. Any function at any call depth can suspend. The caller never annotates.
24Formal

Formal Guarantees

Every safety claim in U is backed by a proved theorem in the formal calculus λ_U.

#TheoremWhat it proves
1Modifier-Optimization CorrespondenceEach annotation is a sound compiler transformation license: -R→stack alloc, -M→no sync, -N→no null check, +A→exact fiber frame
2ARC-FreedomWell-typed -R programs contain zero heap allocations or ARC operations
3Race-FreedomWell-typed programs have no data races
4Proxy SafetyEvery +R +M mutation passes through a proxy satisfying its declared policy
5Type SoundnessWell-typed programs do not get stuck (Progress + Preservation)
6Red-Blue FreedomAll functions may call all functions without async annotation change
7Fiber Frame MinimizationSuspension frame contains exactly the +A-annotated live values — no more
8Signature CompletenessFunction signatures are machine-verified contracts for all three modifier dimensions
9Template SafetyAll {expr} in html/sql/msg are escaped at compile time — injection is a type error
10Localization CompletenessMissing locale keys and placeholder mismatches are compile-time type errors
11Stack-Frame Confinement-R values passed to -R functions cannot be retained beyond the call
12Compilation CorrectnessCompilation to C/WASM preserves semantics and places -R on stack, +R on heap
13Domain Oracle+V/+C/+A domain decisions are exact reads of type annotations — no analysis needed
14.x() SafetyAll .x() iterations are bounded, non-invalidating, and bounds-safe
15Conditional Completeness&, |, ?? are expressively complete for all conditional patterns
16Unsafe Convention SoundnessPrograms with no ! suffixes have no out-of-bounds, divide-by-zero, or unchecked overflow
28Composition

Everything Together — Composability & the Pit of Success

U's type system is not a collection of independent safety features bolted together. Promises, events, exceptions, ownership, and iteration are all expressions of the same underlying model. The pit of success means the natural thing to write is also the correct thing. These scenarios show why.

Scenario 1 — Fetch, fanout, collect, handle

The classic parallel fetch. In JavaScript this requires Promise.all(), .catch(), and careful attention to which errors propagate where. In U the types enforce the structure.

Ufanout.u
d Article { title: S, body: S, author: S }

a f loadDashboard(ids: [I])
    // Error policy declared upfront — happy path reads clean below
    e.x(MyLib.networkPolicies)    // typed array: [log, notify, retry]
    e.x((err: NotFoundError) => showEmpty())

    // Start N concurrent fetches — map returns [Article +A]
    pending: [Article+A] = ids.map((id) => a fetchArticle(id))

    // Await all — input order preserved, time = max not sum
    articles: [Article] = pending.all()

    // Any rejected fetchArticle → NetworkError → e.x() fires above
    // No .catch(), no try/catch, no Promise.all().catch() dance
    render(articles)

Scenario 2 — Race with fallback

Try the cache first; if it times out, fall back to the network. The winner takes it. The loser is ignored. .any() returns T +N +A — nullable because nothing might win.

Urace-fallback.u
a f loadConfig(): Config
    e.x((err: NetworkError) => r => Config.defaults())

    cache: Config+N+A = a fetchFromCache()
    remote: Config+N+A = a fetchFromNetwork()

    winner: Config+N+A = [cache, remote].any()
    r => winner ?? Config.defaults()

Scenario 3 — Event stream with typed dispatch

A UI component subscribes to events, each handler typed. Error handling is a separate concern registered before the event logic. RAII closes the subscription when the component is destroyed.

Uevents.u
d ClickEvent { x: I, y: I, button: I }
d ResizeEvent { width: I, height: I }

d Canvas+R(RAII)
    clicks:  ClickEvent+A+R
    resizes: ResizeEvent+A+R

    f drop(canvas: Canvas+R)   // fires when last ref drops
        canvas.clicks.unsubscribe()   // cleanup automatic

canvas: Canvas+R = Canvas.create()

// Error policy for canvas event handlers
e.x((err: RenderError) => logAndSkip(err))

canvas.clicks.x((evt) => paint(evt.x, evt.y))
canvas.clicks.x((evt) => logInteraction(evt))
canvas.resizes.x((evt) => relayout(evt.width, evt.height))
// No unsubscribe logic — RAII handles it when canvas scope exits

Scenario 4 — Shared state with concurrent workers

Multiple fibers read a config that can be hot-reloaded. MVCC means readers never block. The policy is declared once on the binding; callers need no locking code.

Umvcc.u
config: AppConfig+R+M(MVCC) = AppConfig.load()

// N concurrent workers — all reading config simultaneously
pending: [Result+A] = jobs.map((job) => a runJob(job, config))
results: [Result]   = pending.all()

// Hot-reload fires while workers are running
config.reload()   // MVCC: new version; in-flight workers see old version
// No mutex, no lock, no blocking. Policy handles it.

Why this is the pit of success

Default is safe

Zero annotations = stack-allocated, immutable, non-null, synchronous. The safest possible program requires no effort. Complexity is opt-in via modifiers.

One mechanism, many contexts

.x() handles events, errors, iteration, and policy registration. .all() and .any() handle promise collections. T +N is nullable everywhere. No special cases.

Error handling is a preamble

e.x(policies) before the happy path. Policies are typed arrays — reusable across 100 call sites. The happy path reads as if errors don't exist, because the compiler verified they're covered.

Type-indexed dispatch

Events, errors, and promises all dispatch by type. No string names. No untyped catch-all. The compiler knows exactly what types flow through every channel at every call site.

Policy declared once

RAII, MVCC, Actor, ARC — declared at the binding site. Every consumer gets the right behavior automatically. No per-call locking code, no per-call cleanup code.

Full stack preserved

Stackful fibers. Exceptions carry the complete trace from main through every intermediate caller. Unlike JavaScript where await destroys call context.

29Perspective

On LLMs and the Need for New Languages

A reasonable objection: large language models trained across millions of codebases already know the right patterns. They'll suggest MVCC over a raw mutex, typed errors over untyped exceptions, non-null defaults. Why build a new language when a model can just tell you the correct approach?

The answer is structural. An LLM can recommend MVCC — it cannot prevent you from forgetting to declare the write policy on the next binding. Static checking costs microseconds. Re-inferring a codebase's full dependency graph, policy structure, and modifier discipline from source text — the way current AI coding tools do on every query — costs significant compute and produces educated guesses that can be wrong. A type system encodes verification once; every tool reads it for free thereafter, without re-spending the inference cost.

The deeper point is that the pit of success is a coordination property. It requires that the easy path and the correct path coincide for every developer on every greenfield codebase — not just those who happen to ask the model at the right moment with the right prompt. Language-level enforcement creates a common grammar of correctness that scales uniformly across teams and organizations. Haphazard LLM suggestions across a thousand codebases, each framed differently, produce a thousand dialects. A shared type system produces one.

There's a related argument about tooling. When a codebase declares its structure in types — ownership, mutability, nullability, async boundaries, write policies — tools can understand it instantly from the annotations. When it doesn't, tools must infer the structure from source text on every query, re-reading files, rebuilding call graphs, reasoning about implicit relationships. The cost of that inference scales with codebase size and repeats indefinitely. The cost of reading a type annotation is constant and paid once.

Further reading — tools built around these ideas:

Grokers — Pre-computed knowledge graphs for codebases. Where Cursor and Claude Code re-infer the dependency graph on every query, Grokers indexes it once and makes it queryable in milliseconds. The structural argument made concrete: declared structure is cheaper than inferred structure.

Past the Single-Developer Loop — AI coding when changes need to coordinate across teams. Where Claude Code and Cursor collapse "discussing what to change" and "actually changing it" into one loop, this splits the layers: codebase indexed as a queryable graph, changes routed through governance before any file is written. Structure enforced at the workflow level, not by convention.

Safebox — Sealed compute environments where data sovereignty is a property, not a promise. The keys to read your data are mathematically bound to the box itself. The same principle as U's type system: correctness enforced by architecture, not by asking nicely.