Built-ins, no imports required
Every built-in type, function and capability available in any Capa program. For language syntax and semantics, see the language reference.
Primitive types
| Type | Size / Range | Notes |
|---|---|---|
| Int | 64-bit signed | Arithmetic does not check for overflow |
| Float | 64-bit IEEE 754 | |
| String | UTF-8 | Immutable |
| Bool / Char | true/false · code point | Char is a str of length 1 at runtime |
| Unit | () | For functions with no return value |
String methods
| length() / is_empty() | Code-point count · emptiness |
| to_upper() / to_lower() / trim() | Case and whitespace |
| contains / starts_with / ends_with | Bool |
| split(sep) | List<String> |
| replace(old, new) / substring(start, end) | String (substring clamps out-of-range) |
| char_at(i) / index_of(needle) | Option<String> · Option<Int> |
| bytes() | List<Int>, the UTF-8 bytes |
List<T>
Mutable homogeneous list. Construct with [a, b, c] or by push. Index with xs[i] (unchecked) or get(i) (safe).
| length() / is_empty() / contains(x) | Int · Bool · Bool |
| push(x) | Append (mutation) |
| first() / last() / get(i) | Option<T> |
| map(f) / filter(p) / fold(init, f) | Transform · keep · reduce |
Ranges: a..b (exclusive) and a..=b (inclusive) produce a List<Int> and support the full List API. Float endpoints are excluded; a..b..c is a syntax error.
for i in 0..10 # 0, 1, ..., 9
stdio.println("${i}")
let evens = (0..10).filter(fun (x: Int) -> Bool => x % 2 == 0)Map<K, V>
Hash map. Construct via new_map() with a required type annotation.
| length() / is_empty() / contains_key(k) | Int · Bool · Bool |
| get(k) | Option<V> |
| set(k, v) | Insert / update (mutation) |
| keys() / values() / pairs() | List<K> · List<V> · List<(K, V)> |
Set<T>
Set of unique elements. Construct via new_set() with a type annotation.
| length() / is_empty() / contains(x) | Int · Bool · Bool |
| add(x) / remove(x) | No-op if duplicate / absent |
| to_list() | List<T> |
Option<T>
Built-in sum type Some(T) | None.
| is_some() / is_none() | Bool |
| unwrap_or(default) | T |
| map(f) / and_then(f) / filter(p) / or_else(f) | Option<…> |
| ok_or(err) | Result<T, E> |
Result<T, E>
Built-in sum type Ok(T) | Err(E). The ? operator propagates Err in functions that return Result.
| is_ok() / is_err() / unwrap_or(d) | Bool · Bool · T |
| map(f) / and_then(f) / map_err(f) / or_else(f) | Result<…> |
| ok() / err() | Option<T> · Option<E> |
JsonValue
Built-in sum type: JNull | JBool(Bool) | JNum(Float) | JStr(String) | JArr(List<JsonValue>) | JObj(Map<String, JsonValue>).
| is_null() | Bool |
| as_bool() / as_num() / as_string() | Option<Bool> · Option<Float> · Option<String> |
| as_array() / as_object() | Option<List<…>> · Option<Map<…>> |
| parse_json(s) / to_json(j) | Result<JsonValue, String> · String |
Conversions & panic
Capa has no implicit numeric coercion: Float + Int is a type error. Convert explicitly at the call site.
| parse_int(s) / parse_float(s) | Option<Int> · Option<Float> |
| to_float(i) / to_int(f) | Float · Int (truncates toward zero) |
| new_map() / new_set() | Require a let annotation to pin the types |
panic(message) terminates the program immediately: no unwinding, no catch. panic: <message> is written to stderr and the process exits non-zero. The contract is identical on every backend (Wasm and Component Model trap, translated to exit 1).
Python interoperability
Both functions cross the Capa/Python trust boundary and require Unsafe as the first argument. Crossing loses Capa's static guarantees: the Python value can do anything its type allows, with full ambient authority. --manifest marks such functions has_unsafe: true.
fun square_root(unsafe: Unsafe, x: Float) -> Float
let math = py_import(unsafe, "math")
return py_invoke(unsafe, math.sqrt, [x])Capabilities
Stdio
print(s), println(s), eprintln(s) (stderr), read_line() -> Result<String, IoError>.
Fs
| read(p) / write(p, c) | Result<String, IoError> · Result<(), IoError> |
| exists(p) / is_dir(p) | Bool (false on denied paths) |
| mkdir(p) / list_dir(p) | Result<(), …> · Result<List<String>, …> |
| restrict_to(prefix) / allows(path) | Attenuated Fs (monotonic) · query without I/O |
Env · Clock · Random
| env.get(name) / env.args() | Option<String> · List<String> |
| env.restrict_to_keys(keys) | Attenuated Env |
| clock.now_secs() / now_monotonic() / sleep(s) | Float · Float · () |
| clock.restrict_to_after(t) | Active only after timestamp (takes the maximum threshold) |
| random.int_range(low, high) / float_unit() | Int in [low,high) · Float in [0,1) |
| random.with_seed(seed) | Deterministic sequence |
Net
A Net from main is unrestricted; restrict_to(host) returns a fresh Net whose authority is the intersection with {host} (monotonic). get(url) returns Err immediately if the host is outside the restriction set, before any system call.
fun main(net: Net, stdio: Stdio)
let api = net.restrict_to("api.example.com")
match fetch(api)
Ok(body) -> stdio.println(body)
Err(e) -> stdio.eprintln("${e}")Db
SQLite-backed database with path-prefix attenuation (mirrors Fs). query returns the rows as a JSON-encoded array of arrays of strings, so the wire shape is a single form; callers parse with parse_json and project columns explicitly.
| exec(path, sql) | Result<(), IoError>, runs DDL / DML (multiple ;-separated statements supported) |
| query(path, sql) | Result<String, IoError>, JSON-encoded rows |
| restrict_to(prefix) / allows(path) | Attenuated Db (monotonic path prefix) · query without I/O |
Proc
Sandboxed subprocess execution with basename-prefix attenuation. exec takes the command and a JSON-encoded argv tail (for example ["status", "--short"]); stdout is returned as a String. allows checks the basename on a suffix boundary (restrict_to("git") admits git and git-lfs but not gitlab).
| exec(cmd, args_json) | Result<String, IoError>, the process stdout |
| restrict_to(cmd_prefix) / allows(cmd) | Attenuated Proc (basename prefix) · query without spawning |
Serve
Authority to listen on a TCP address and do I/O on inbound connections. Every other capability reaches out; Serve is the one that is reached. It is deliberately connection-level, not HTTP-level: the trusted runtime binds, accepts and moves bytes, so protocol parsing is ordinary Capa code in a library (see capa_server) rather than trusted-runtime code.
| restrict_to(spec) / allows(addr, port) | Attenuated, un-bound Serve over (bind address, port) · query without I/O |
| listen(addr, port) / local_port() | Result<(), IoError> (denied before any socket exists) · Result<Int, IoError>, the port actually bound |
| accept() | Result<Int, IoError>, a connection id (ids start at 1) |
| recv(conn, max_bytes) / send(conn, bytes) | Result<List<Int>, IoError>, an empty list is EOF · Result<(), IoError>, each byte masked with & 0xFF |
| close(conn) / stop() | Close one connection · close the listener and any open connection |
A spec is "addr:port", "addr:lo-hi" or "addr:*", with "*" also accepted as the address. Rules accumulate: a bind must satisfy every rule taken so far, so restrict_to only ever narrows and restrict_to("*:*") on an already-narrowed capability restores nothing. A spec that does not parse denies everything, because silently ignoring a typo would widen authority. Enforcement runs before the syscall, so a denied address or port is never bound, not even transiently.
The model is sequential: one open connection at a time, no threads and no async. A second accept while a connection is open returns Err telling the caller to close first. accept, recv and send are bounded at 30 seconds and return Err on expiry rather than hanging. Honest limits: IPv4 only, addresses matched by exact string equality ("127.0.0.1" and "localhost" are different rules), and the check runs on the port you request, so a rule admitting port 0 admits an arbitrary ephemeral port. On an Err from send the number of bytes already transmitted is unspecified: close the connection rather than retry.
recv is the language's one inbound information-flow source and its bytes are @public; send is a public sink on its payload argument only. The lattice models confidentiality, not integrity or taint: @public on an attacker-controlled request asserts only that it is not a secret whose disclosure the analysis must prevent, and asserts nothing about it being trustworthy or safe to act on. See the information-flow section.
fun echo_once(serve: Serve, conn: Int) -> Result<Unit, IoError>
let request = serve.recv(conn, 1024)?
return serve.send(conn, request)
fun main(stdio: Stdio, serve: Serve)
let local = serve.restrict_to("127.0.0.1:8080")
match local.listen("127.0.0.1", 8080)
Ok(_) -> match local.accept()
Ok(conn) ->
match echo_once(local, conn)
Ok(_) -> stdio.println("echoed")
Err(e) -> stdio.eprintln("${e}")
let _ = local.close(conn)
let _ = local.stop()
Err(e) -> stdio.eprintln("${e}")
Err(e) -> stdio.eprintln("${e}")User-defined capabilities
Libraries declare their own capabilities with the capability keyword; any type that impls it becomes a valid implementor. A cap-bearing struct may hold built-in caps as fields. Only call / method-call right-hand sides produce fresh capability instances that can be bound; a plain alias let dup = mailer is rejected.
capability SendEmail
fun send(self, to: String, subject: String, body: String) -> Result<Unit, IoError>
impl SendEmail for SmtpMailer
fun send(self, ...) -> Result<Unit, IoError>
return Ok(())The IoError type
Opaque type for I/O errors, available as the error parameter in Result<T, IoError> and in pattern matching. Its string representation is human-readable; the internal contents are private.
match fs.read("x.txt")
Ok(content) -> stdio.println(content)
Err(e) -> stdio.eprintln("error: ${e}")