20 Capabilities

Capabilities are traits representing effects or suspension.

Grammar: See grammar.ebnf § DECLARATIONS (uses_clause), EXPRESSIONS (with_expr)

20.1 Declaration

@fetch (url: str) -> Result<Response, Error> uses Http = Http.get(url);

@save (data: str) -> Result<void, Error> uses FileSystem, Suspend =
    FileSystem.write(path: "/data.txt", content: data);

20.2 Capability traits

Capabilities are traits with default implementations:

pub trait Http {
    @get (url: str) -> Result<Response, Error>;
    @post (url: str, body: str) -> Result<Response, Error>;
}

pub def impl Http {
    @get (url: str) -> Result<Response, Error> = ...;
    @post (url: str, body: str) -> Result<Response, Error> = ...;
}

Import the trait to use the default:

use std.net.http { Http };

@fetch () -> Result<str, Error> uses Http =
    Http.get(url: "https://api.example.com/data");

Other standard capability traits:

trait FileSystem {
    @read (path: str) -> Result<str, Error>;
    @write (path: str, content: str) -> Result<void, Error>;
}

trait Print {
    @print (msg: str) -> void;
    @println (msg: str) -> void;
    @output () -> str;
    @clear () -> void;
}

20.3 Suspend capability

Suspend is a marker capability indicating a function may suspend. A function with uses Suspend requires a suspending context to execute.

With uses SuspendWithout
Non-blocking, may suspendBlocking, synchronous
@fetch_suspending (url: str) -> Result<Data, Error> uses Http, Suspend = ...;  // may suspend
@fetch_sync (url: str) -> Result<Data, Error> uses Http = ...;                 // blocks

No async type modifier. No .await expression. Return type is final value type.

Concurrency via parallel pattern:

parallel(tasks: [fetch(a), fetch(b)], max_concurrent: 10)

See Concurrency Model for task definitions, suspending context semantics, and suspension points.

20.4 Providing capabilities

Default implementations are automatically bound when importing a trait. Override with with...in when custom configuration or mocking is needed:

with Http = ConfiguredHttp { timeout: 5s } in
    fetch("/data")

20.4.1 Multi-binding syntax

Multiple capabilities may be bound in a single with expression using comma-separated bindings:

with Http = mock_http, Cache = mock_cache in
    complex_operation()

This is equivalent to nested with expressions:

with Http = mock_http in
    with Cache = mock_cache in
        complex_operation()

20.4.2 Partial provision

When a function requires multiple capabilities, some may be explicitly provided while others use defaults:

def impl Http { ... }
def impl Cache { ... }
def impl Logger { ... }

@test_with_mock_http () -> void = {
    let mock = MockHttp { ... };

    with Http = mock in
        complex_operation(),  // MockHttp + default Cache + default Logger
}

Only Http is overridden; Cache and Logger use their def impl.

20.4.3 Nested binding semantics

Inner bindings shadow outer bindings within their scope:

with Http = OuterHttp in {
    use_http(),  // OuterHttp

    with Http = InnerHttp in
        use_http(),  // InnerHttp (shadows Outer)

    use_http(),  // OuterHttp again
}

with creates a lexical scope — bindings are visible only within:

let result = with Http = mock in fetch();
// mock is NOT bound here
fetch()  // Uses default Http, not mock

20.5 Stateful handlers

Grammar: See grammar.ebnf § EXPRESSIONS (handler_expr)

Proposal: stateful-mock-testing-proposal.md

A stateful handler is a with...in binding that threads local mutable state through handler operations. The handler(state: expr) { ... } construct creates a handler frame with frame-local state, enabling stateful capability mocking while preserving value semantics.

handler is a context-sensitive keyword, valid only in the expression position of a capability_binding.

20.5.1 Syntax

with Counter = handler(state: 0) {
    increment: (s) -> (s + 1, s + 1),
    get: (s) -> (s, s),
} in {
    let a = Counter.increment(),  // state: 0 -> 1, returns 1
    let b = Counter.increment(),  // state: 1 -> 2, returns 2
    a + b,                        // 3
}

20.5.2 Semantics

  1. The state: initializer determines the initial state value and its type S
  2. Each handler operation receives the current state as its first argument, replacing self
  3. Each handler operation shall return (S, R) where S is the next state and R is the trait method’s return type
  4. State is threaded through operations sequentially within the with...in scope
  5. The with...in expression returns the body’s type; handler state is internal

20.5.3 Type checking rules

For a handler operation named op implementing trait method @op (self, p1: T1, ..., pN: TN) -> R:

  • The handler operation receives (state: S, p1: T1, ..., pN: TN)
  • The handler operation shall return (S, R)
  • The state type S is inferred from the state: initializer
  • All handler operations shall use the same state type
  • Every required trait method shall have a corresponding handler operation; default trait methods are used if not overridden
  • Handler operations for non-existent trait methods are an error

A stateful handler is not a type and has no self. It satisfies the trait’s interface for the duration of the with...in scope through a distinct dispatch mechanism from impl blocks.

20.5.4 State composition

Handlers support a single state value. Multiple independent state values are composed into a struct or tuple:

with Counter = handler(state: { count: 0, log: [] }) {
    increment: (s) -> ({ ...s, count: s.count + 1, log: [...s.log, "inc"] }, s.count + 1),
    get: (s) -> (s, s.count),
} in ...

20.5.5 Nested handlers

Each handler maintains independent state. Cross-handler calls dispatch through normal capability resolution:

with Logger = handler(state: []) {
    log: (s, msg: str) -> ([...s, msg], ()),
} in
    with Counter = handler(state: 0) {
        increment: (s) -> {
            Logger.log(msg: "increment"),  // invokes outer handler
            (s + 1, s + 1)
        },
    } in ...

20.5.6 Restrictions

  • def impl cannot be stateful (stateless by design, no scope for state lifetime)
  • Stateful handlers are available in all with...in scopes (not restricted to test code)

20.6 Capability variance

A context with more capabilities may call functions requiring fewer:

@needs_http () -> void uses Http = ...;
@needs_both () -> void uses Http, Cache = ...;

@caller () -> void uses Http, Cache = {
    needs_http(),  // OK: caller has Http
    needs_both(),  // OK: caller has both
}

A function requiring more capabilities cannot be called from one with fewer:

@needs_both () -> void uses Http, Cache = ...;

@caller () -> void uses Http = {
    needs_both(),  // ERROR: caller lacks Cache
}

20.7 Propagation

Capabilities propagate: if A calls B with capability C, A shall declare or provide C.

@helper () -> str uses Http = Http.get("/").body;

// Must declare Http
@caller () -> str uses Http = helper();

// Or provide it
@caller () -> str = with Http = MockHttp {} in helper();

20.8 Standard capabilities

CapabilityPurposeMay Suspend
HttpHTTP clientYes
FileSystemFile I/OYes
CacheCachingYes
ClockTimeNo
RandomRNGNo
CryptoCryptographic operationsNo
PrintStandard outputNo
LoggerStructured loggingNo
EnvEnvironmentNo
IntrinsicsLow-level SIMD and bit operationsNo
FFIForeign function interfaceNo
SuspendSuspension markerYes
UnsafeSafety bypass markerNo

20.8.1 Cache capability

The Cache capability provides key-value caching with TTL-based expiration:

trait Cache {
    @get<K: Hashable + Eq, V: Clone> (self, key: K) -> Option<V>;
    @set<K: Hashable + Eq, V: Clone> (self, key: K, value: V, ttl: Duration) -> void;
    @invalidate<K: Hashable + Eq> (self, key: K) -> void;
    @clear (self) -> void;
}

Used by the cache pattern (see Patterns § cache).

Cache implementations differ in suspension behavior:

ImplementationDescriptionSuspends
InMemoryCacheProcess-localNo
DistributedCacheShared across nodesYes
NoOpCacheDisable caching (always miss)No

When using a suspending cache implementation, the calling function shall have uses Suspend or be called from a suspending context.

20.8.2 Clock capability

The Clock capability provides access to the current time:

trait Clock {
    @now () -> Instant;
    @local_timezone () -> Timezone;
}

Instant and Timezone are defined in std.time. Functions requiring current time shall declare uses Clock:

@log_timestamp (msg: str) -> void uses Clock, Print =
    print(msg: `[{Clock.now()}] {msg}`);

Mock clocks enable deterministic testing via stateful handlers:

@test_expiry tests @is_expired () -> void = {
    let start = Instant.from_unix_secs(secs: 1700000000);
    with Clock = handler(state: start) {
        now: (s) -> (s, s)
        advance: (s, by: Duration) -> (s + by, ())
    } in {
        assert(!is_expired(token: token));
        Clock.advance(by: 1h);
        assert(is_expired(token: token))
    }
}

The handler(state: expr) { ... } construct creates a stateful handler frame with local mutable state threaded through operations. State is frame-local and does not violate value semantics. See Stateful Handlers for the full specification.

20.8.3 Crypto capability

The Crypto capability provides cryptographic operations:

trait Crypto {
    @hash (data: [byte], algorithm: HashAlgorithm) -> [byte];
    @hash_password (password: str) -> str;
    @verify_password (password: str, hash: str) -> bool;
    @generate_key () -> SecretKey;
    @encrypt (key: SecretKey, plaintext: [byte]) -> [byte];
    @decrypt (key: SecretKey, ciphertext: [byte]) -> Result<[byte], CryptoError>;
    @random_bytes (count: int) -> [byte];
    // ... additional methods defined in std.crypto
}

Types and functions are defined in std.crypto. The Crypto capability is non-suspending — cryptographic operations are CPU-bound and complete synchronously.

Key types are separated by purpose to prevent misuse at compile time:

  • SigningPrivateKey / SigningPublicKey — for digital signatures
  • EncryptionPrivateKey / EncryptionPublicKey — for asymmetric encryption
  • KeyExchangePrivateKey / KeyExchangePublicKey — for Diffie-Hellman key exchange

Private key types (SecretKey, SigningPrivateKey, EncryptionPrivateKey, KeyExchangePrivateKey) automatically zero their memory when dropped.

20.8.4 Intrinsics capability

The Intrinsics capability provides low-level SIMD operations, bit manipulation, and hardware feature detection:

trait Intrinsics {
    // SIMD operations (examples for 4-wide float)
    @simd_add_f32x4 (a: [float, max 4], b: [float, max 4]) -> [float, max 4];
    @simd_mul_f32x4 (a: [float, max 4], b: [float, max 4]) -> [float, max 4];
    @simd_sum_f32x4 (a: [float, max 4]) -> float;
    // ... additional widths: f32x8, f32x16, i64x2, i64x4

    // Bit operations
    @count_ones (value: int) -> int;
    @count_leading_zeros (value: int) -> int;
    @count_trailing_zeros (value: int) -> int;
    @rotate_left (value: int, amount: int) -> int;
    @rotate_right (value: int, amount: int) -> int;

    // Hardware queries
    @cpu_has_feature (feature: str) -> bool;
}

SIMD operations work on fixed-capacity lists representing vector registers:

WidthFloat TypeInt TypePlatforms
128-bit[float, max 4][int, max 2]SSE, NEON, SIMD128
256-bit[float, max 8][int, max 4]AVX, AVX2
512-bit[float, max 16]AVX-512

The default def impl Intrinsics uses native SIMD instructions when available and falls back to scalar emulation otherwise. For testing, EmulatedIntrinsics always uses scalar operations.

Feature detection via cpu_has_feature accepts platform-specific feature strings:

PlatformFeatures
x86_64"sse", "sse2", "sse3", "sse4.1", "sse4.2", "avx", "avx2", "avx512f"
aarch64"neon"
wasm32"simd128"

Unknown feature strings cause a panic.

20.9 Default capabilities

Print has a default implementation via def impl. Programs may use print without declaring uses Print:

@main () -> void = print(msg: "Hello, World!");

The default is StdoutPrint for native execution, BufferPrint for WASM.

20.9.1 Name resolution

When resolving a capability name, the compiler checks in order:

  1. Innermost with...in binding — highest priority
  2. Outer with...in bindings — in reverse nesting order
  3. Imported def impl — from the module where the trait is defined
  4. Module-local def impl — defined in the current module
  5. Error — capability not provided

When both an imported def impl and a module-local def impl exist for the same capability, imported takes precedence.

20.9.2 Marker capabilities

Proposal: unsafe-semantics-proposal.md

A marker capability is a capability with no methods, no bindable implementation, and a specific discharge mechanism. Marker capabilities gate operations or contexts — they track what the code does, not what API it uses.

Shared semantics for all marker capabilities:

  • No methods — gates operations, not API surface
  • Cannot be bound via with...in (E1203)
  • Propagates through the call chain like any capability
  • Each marker has its own discharge mechanism
MarkerPurposeDischarge
SuspendMay suspend executionRuntime / concurrency patterns
UnsafeBypasses safety guaranteesunsafe { } block

Attempting to bind a marker capability is a compile-time error:

with Suspend = SomeImpl in  // ERROR: marker capability cannot be bound
    suspending_fn()

with Unsafe = something in  // ERROR: marker capability cannot be bound
    unsafe_fn()

Suspend context is provided by:

  • The runtime for @main () uses Suspend
  • Concurrency patterns: parallel, spawn, nursery

Unsafe context is provided by:

  • unsafe { expr } blocks — programmer assertion of safety
  • Functions declaring uses Unsafe (propagates to callers)

See FFI § Unsafe Expressions for the full specification of unsafe operations.

20.10 Testing

@test_fetch tests @fetch () -> void =
    with Http = MockHttp { responses: {"/users/1": "{...}"} } in
    {
        assert_ok(result: fetch(url: "/users/1"))
    }

Mock implementations are synchronous; test does not need Suspend.

20.11 Named capability sets (capset)

Grammar: See grammar.ebnf § DECLARATIONS (capset_decl)

A capset is a named, transparent alias for a set of capabilities. Capsets are expanded to their constituent capabilities during name resolution, before type checking. A capset is not a trait, not a type, and has no runtime representation.

capset Net = Http, Dns, Tls
capset Runtime = Clock, Random, Env
capset WebService = Net, Runtime, Database, Suspend

20.11.1 Usage

Capsets may appear in uses clauses and other capset declarations. Capsets and individual capabilities may be mixed:

@fetch (url: str) -> Result<str, Error> uses Net, Logger, Suspend = ...;

After expansion, uses Net, Logger, Suspend is equivalent to uses Http, Dns, Tls, Logger, Suspend.

20.11.2 Expansion

The compiler expands capsets transitively and deduplicates the result. The expanded set uses set semantics — duplicates are eliminated and order is irrelevant:

capset Net = Http, Dns
capset Infra = Net, Logger

// Valid — `uses Infra, Http` expands to `uses Http, Dns, Logger`
@fn () -> void uses Infra, Http = ...;

20.11.3 Restrictions

A capset declaration:

  • Must contain at least one member
  • Must not form a cycle with other capset declarations
  • Must not share a name with a trait in the same scope
  • Members shall be capability traits or other capsets

A capset is not a trait. It cannot be used in impl blocks, def impl declarations, or with...in bindings:

// Invalid — capsets cannot be bound
with Net = something in ...  // error

// Invalid — capsets are not traits
impl SomeType: Net { ... }  // error
def impl Net { ... }           // error

20.11.4 Visibility

Capsets follow standard visibility rules. A pub capset shall not reference non-accessible capabilities:

pub capset Net = Http, Dns, Tls       // Valid — all members accessible
pub capset Bad = SomePrivateTrait      // Invalid — member not accessible

20.11.5 Variance interaction

Capability variance operates on the expanded set:

capset Runtime = Clock, Random, Env

@needs_clock () -> void uses Clock = ...;

@caller () -> void uses Runtime = {
    needs_clock(),  // Valid — Runtime includes Clock
}

20.12 Purity

Functions without uses are pure: no side effects, cannot suspend, safely parallelizable.

20.13 Errors

CodeDescription
E0600Function uses capability without declaring it
E1000Conflicting default implementations for same trait
E1001Duplicate default implementation in same module
E1002def impl methods cannot have self parameter
E1200Missing capability (callee requires capability caller lacks)
E1201Unbound capability (no with or def impl available)
E1202Type does not implement capability trait
E1203Marker capability cannot be explicitly bound (Suspend, Unsafe)
E1204Handler missing required operation (trait method not defined in handler)
E1205Handler operation signature mismatch (parameters or return type)
E1206Handler state type inconsistency (operations return different state types)
E1207Handler operation for non-existent trait method
E1220Cyclic capset definition
E1221Empty capset
E1222Capset name collides with trait name
E1223Capset member is not a capability trait or capset
error[E0600]: function uses `Http` without declaring it

Capabilities shall be explicitly declared or provided.