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> |
| find_index(pred) | Option<Int>, the code-point index of the first character satisfying pred. Where index_of asks "where is this substring", this asks "where is the first character like this". |
| split_once(sep) | Option<(String, String)>, the receiver cut at the first occurrence of sep; the separator is in neither part. "k=v=w".split_once("=") is Some(("k", "v=w")) where split("=") would give three parts. |
| lines() | List<String>, the lines with their terminators (\r\n, \n or a lone \r) removed. A trailing terminator yields no phantom empty element, which is what distinguishes it from split("\n"). |
| 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) / pop() | Append (mutation) · remove the last element and return it as Option<T>, None on an empty list (mutation) |
| first() / last() / get(i) | Option<T> |
| map(f) / filter(p) / fold(init, f) | Transform · keep · reduce |
| find(p) / find_index(p) | Option<T> · Option<Int>, the first element (or its index) matching p |
| sorted() / sorted_by(cmp) | Fresh sorted copy, stable, receiver unchanged. sorted() uses the order the compiler supplies; sorted_by uses your comparator, negative / 0 / positive as in C's qsort. |
| min() / max() | Option<T>, None on an empty list. Same order as sorted(), so xs.min() and xs.sorted().first() always agree. |
What sorted() accepts. The element type must be one the ordering operators accept: Int, Float, String or Char. Anything else is a compile error naming the type, and pointing at the comparator form:
error: method 'sorted': List<Bool> has no order the compiler can supply;
Bool is not accepted by the ordering operators either. Use sorted_by with
your own comparator to order it.The compiler-supplied order is total on Float: NaN sorts after every number, so min() answers the smallest real number and max() answers NaN, each agreeing with sorted().first() / sorted().last(). This matters because a sort built on < alone would misplace the non-NaN elements too, and differently on each backend. sorted_by does not get this treatment: there the comparator is yours, and one that is not a total order has undefined results, by the same reasoning that applies to qsort. That is a divergence the language does not paper over, so a sorted_by result is only as backend-stable as your comparator is total.
Ranges: a..b (exclusive) and a..=b (inclusive) carry the transforming and indexed-query methods (map, filter, fold, find, find_index, first, last, get, contains, length, is_empty, to_list), with the same semantics as their List homonyms. The List methods not declared on a Range are sorted, sorted_by, min, max, reverse, enumerate, zip, flat_map and the mutating push / pop; calling one reports type 'Range' has no method '<name>'. Reach them through to_list(). 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) |
| remove(k) | Option<V>: remove the entry for k and return the value it held, or None if the key is absent (mutation). Surviving entries keep their insertion order. It returns the value, because the caller already supplied the key and a separate get beforehand would not be atomic with the removal. (Set.remove(x) returns nothing, for the same reason in reverse.) |
| filter(pred) | A fresh Map<K, V> of the pairs for which pred(k, v) is true, in the receiver's insertion order; the receiver is not mutated. The predicate takes the key and the value, because a Map entry is both. |
| 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}")