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 Suspend | Without |
|---|---|
| Non-blocking, may suspend | Blocking, 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
- The
state:initializer determines the initial state value and its typeS - Each handler operation receives the current state as its first argument, replacing
self - Each handler operation shall return
(S, R)whereSis the next state andRis the trait method’s return type - State is threaded through operations sequentially within the
with...inscope - The
with...inexpression 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
Sis inferred from thestate: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 implcannot be stateful (stateless by design, no scope for state lifetime)- Stateful handlers are available in all
with...inscopes (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
| Capability | Purpose | May Suspend |
|---|---|---|
Http | HTTP client | Yes |
FileSystem | File I/O | Yes |
Cache | Caching | Yes |
Clock | Time | No |
Random | RNG | No |
Crypto | Cryptographic operations | No |
Print | Standard output | No |
Logger | Structured logging | No |
Env | Environment | No |
Intrinsics | Low-level SIMD and bit operations | No |
FFI | Foreign function interface | No |
Suspend | Suspension marker | Yes |
Unsafe | Safety bypass marker | No |
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:
| Implementation | Description | Suspends |
|---|---|---|
InMemoryCache | Process-local | No |
DistributedCache | Shared across nodes | Yes |
NoOpCache | Disable 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 signaturesEncryptionPrivateKey/EncryptionPublicKey— for asymmetric encryptionKeyExchangePrivateKey/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:
| Width | Float Type | Int Type | Platforms |
|---|---|---|---|
| 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:
| Platform | Features |
|---|---|
| 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:
- Innermost
with...inbinding — highest priority - Outer
with...inbindings — in reverse nesting order - Imported
def impl— from the module where the trait is defined - Module-local
def impl— defined in the current module - 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
| Marker | Purpose | Discharge |
|---|---|---|
Suspend | May suspend execution | Runtime / concurrency patterns |
Unsafe | Bypasses safety guarantees | unsafe { } 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
| Code | Description |
|---|---|
| E0600 | Function uses capability without declaring it |
| E1000 | Conflicting default implementations for same trait |
| E1001 | Duplicate default implementation in same module |
| E1002 | def impl methods cannot have self parameter |
| E1200 | Missing capability (callee requires capability caller lacks) |
| E1201 | Unbound capability (no with or def impl available) |
| E1202 | Type does not implement capability trait |
| E1203 | Marker capability cannot be explicitly bound (Suspend, Unsafe) |
| E1204 | Handler missing required operation (trait method not defined in handler) |
| E1205 | Handler operation signature mismatch (parameters or return type) |
| E1206 | Handler state type inconsistency (operations return different state types) |
| E1207 | Handler operation for non-existent trait method |
| E1220 | Cyclic capset definition |
| E1221 | Empty capset |
| E1222 | Capset name collides with trait name |
| E1223 | Capset member is not a capability trait or capset |
error[E0600]: function uses `Http` without declaring it
Capabilities shall be explicitly declared or provided.