Standard Library
Built into the compiler — no imports needed. Every function here is available in every .runa file. User-defined functions with the same name shadow builtins.
Display
| Function | Signature | Description |
|---|---|---|
show | a -> String | Convert any value to its string representation |
print | String -> () | Print to stdout (use via @ print(...)) |
@ print(show(42)) -- 42
@ print(show([1, 2, 3])) -- [1, 2, 3]
Printed strings have no surrounding quotation marks. show is a display
format; use the JSON calculation interface when a tool needs structured values.
show gives the same text in interpreted and compiled execution:
| Value | Display |
|---|---|
String, Char | the text itself, also inside lists, records and streams (["v1.0", "a { b }"] → [v1.0, a { b }], 'c' → c) |
Float | shortest decimal that reads back as the same value, never exponent notation; whole values have no .0 (22.0 → 22, 1e21 → 1000000000000000000000, 0.000001 → 0.000001) |
List, stream | [a, b] |
Pair(a, b), tuple | (a, b) — a Pair is a two-element tuple |
| record, constructor | Name(field: value) for named fields, Name(a, b) for positional fields, Name without fields |
Option, Result | Some(x), None, Ok(x), Err(e) |
Map, Set | {k: v, ...}, {a, b} in value order |
Value order
sort, sort_by, list_min, list_max and the iteration of every Map
(map_keys, map_values, map_entries, show) and Set use one order:
numbers numerically, String and Char by Unicode code point, false before
true, lists and tuples element by element (a prefix first), and constructor
values by constructor name, then by their fields in declaration order.
@ print(show(sort([10, 9, 100]))) -- [9, 10, 100]
@ print(show(map_keys(map_from([(10, "a"), (9, "b")])))) -- [9, 10]
Math
| Function | Signature | Description |
|---|---|---|
abs | Int -> Int | Absolute value |
sqrt | Float -> Float | Square root |
pow | (Float, Float) -> Float | Exponentiation |
exp | Float -> Float | Natural exponential (e^x) |
ln | Float -> Float | Natural logarithm |
round | Float -> Int | Round to nearest integer |
floor | Float -> Int | Floor (round down) |
to_float | Int -> Float | Convert integer to float |
max_int | (Int, Int) -> Int | Maximum of two integers |
min_int | (Int, Int) -> Int | Minimum of two integers |
max_f | (Float, Float) -> Float | Maximum of two floats |
min_f | (Float, Float) -> Float | Minimum of two floats |
clamp | (Int, Int, Int) -> Int | Clamp value to range [lo, hi] |
= x = abs(-7) -- 7
= r = sqrt(16.0) -- 4.0
= p = pow(2.0, 10.0) -- 1024.0
= n = round(3.7) -- 4
= f = floor(3.7) -- 3
= c = clamp(15, 0, 10) -- 10
= big = max_int(3, 7) -- 7
String Operations
String positions and lengths use Unicode scalar values, matching Rust char
iteration. They are not UTF-8 byte offsets and not grapheme clusters:
string_length("å🙂b") == 3, char_at("å🙂b", 1) == "🙂", and
index_of("å🙂b", "b") == 2. When length is applied to a String, it has
the same scalar-count behavior as string_length; for lists, length still
counts elements. substring clamps negative starts/lengths to zero and stops at
the string end. char_at returns "" when the index is out of range.
| Function | Signature | Description |
|---|---|---|
string_length | String -> Int | Number of Unicode scalar values in string |
split | (String, String) -> List(String) | Split by separator |
join | (List(String), String) -> String | Join with separator |
trim | String -> String | Remove leading/trailing whitespace |
contains | (String, String) -> Bool | Substring test |
starts_with | (String, String) -> Bool | Prefix test |
ends_with | (String, String) -> Bool | Suffix test |
replace | (String, String, String) -> String | Replace all occurrences |
to_upper | String -> String | Convert to uppercase |
to_lower | String -> String | Convert to lowercase |
substring | (String, Int, Int) -> String | Extract by scalar start index and scalar length |
char_at | (String, Int) -> String | Single Unicode scalar value by index |
index_of | (String, String) -> Int | Find substring scalar position (-1 if absent) |
format_float | (Float, Int) -> String | Format float with N decimal places |
parse_int | String -> Result(Int, String) | Parse an integer; Err for invalid text |
parse_float | String -> Result(Float, String) | Parse a finite number; Err for invalid text |
string_chars | String -> List(String) | Explode into Unicode scalar values |
format_float accepts precision from 0 through 65535 inclusive. Values outside
this range fail with a diagnostic before formatting; calculations retain their
case-local error handling. Arguments are evaluated once, in source order.
parse_int and parse_float trim surrounding whitespace and do not use the
file's language or the computer's locale to interpret separators:
parse_float("1,5") and parse_int("1.000") are Err, like any other invalid
input. parse_float rejects inf and NaN. Use = n <- parse_int(text) to
return the error from the enclosing function, or match on Ok/Err.
= parts = split("a,b,c", ",") -- ["a", "b", "c"]
= joined = join(["x", "y"], "-") -- "x-y"
= clean = trim(" hello ") -- "hello"
= has = contains("hello world", "world") -- true
= upper = to_upper("hello") -- "HELLO"
= sub = substring("the cat sat", 4, 5) -- "cat s"
= ch = char_at("hello", 0) -- "h"
= pos = index_of("hello", "ll") -- 2
= fmt = format_float(3.14159, 2) -- "3.14"
= n = parse_int("42") -- Ok(42)
= chars = string_chars("abc") -- ["a", "b", "c"]
= replaced = replace("foo bar foo", "foo", "baz") -- "baz bar baz"
List Operations
| Function | Signature | Description |
|---|---|---|
length | List(a) -> Int or String -> Int | List elements or Unicode scalar values |
head | List(a) -> a | First element |
tail | List(a) -> List(a) | All but first |
push | (List(a), a) -> List(a) | Append element |
concat | (List(a), List(a)) -> List(a) | Concatenate two lists |
reverse | List(a) -> List(a) | Reverse a list |
map | (List(a), a -> b) -> List(b) | Transform each element |
filter | (List(a), a -> Bool) -> List(a) | Keep elements matching predicate |
foldl | (List(a), b, (b, a) -> b) -> b | Left fold |
range | (Int, Int) -> List(Int) | Range [start, end) |
= xs = [3, 1, 4, 1, 5]
= len = length(xs) -- 5
= first = head(xs) -- 3
= rest = tail(xs) -- [1, 4, 1, 5]
= bigger = push(xs, 9) -- [3, 1, 4, 1, 5, 9]
= both = concat([1, 2], [3, 4]) -- [1, 2, 3, 4]
= rev = reverse(xs) -- [5, 1, 4, 1, 3]
= doubled = map(xs, |x| x * 2) -- [6, 2, 8, 2, 10]
= big = filter(xs, |x| x > 2) -- [3, 4, 5]
= total = foldl(xs, 0, |acc, x| acc + x) -- 14
= nums = range(1, 5) -- [1, 2, 3, 4]
head([]) is a runtime error. tail([]) returns [].
Out-of-range direct indexing (xs[i]) and nth(xs, i) are runtime errors.
Collection Operations (Kotlin-inspired)
Higher-order operations on lists. All work in both interpreter and compiled mode.
| Function | Signature | Description |
|---|---|---|
sort | List(a) -> List(a) | Sort in value order (see Display) |
sort_by | (List(a), a -> b) -> List(a) | Sort by key function |
any | (List(a), a -> Bool) -> Bool | True if any element matches |
all | (List(a), a -> Bool) -> Bool | True if all elements match |
find | (List(a), a -> Bool) -> Option(a) | First matching element |
flat_map | (List(a), a -> List(b)) -> List(b) | Map then flatten |
zip | (List(a), List(b)) -> List(Pair(a, b)) | Pair elements from two lists |
enumerate | List(a) -> List(Pair(Int, a)) | Index-value pairs |
take_while | (List(a), a -> Bool) -> List(a) | Take while predicate holds |
drop_while | (List(a), a -> Bool) -> List(a) | Drop while predicate holds |
sum_list | List(Int) -> Int | Sum of integer list |
distinct | List(a) -> List(a) | Remove duplicates (preserves order) |
count_by | (List(a), a -> Bool) -> Int | Count elements matching predicate |
partition | (List(a), a -> Bool) -> (List(a), List(a)) | Split by predicate |
chunked | (List(a), Int) -> List(List(a)) | Split into chunks of size N; a size of 0 or less is an error |
subscribe | (List(a), a -> ()) -> () | Iterate and apply callback |
The callback of filter, any, all and find answers Bool. A callback
whose result type is known to be anything else is a type error; a callback
whose result type is only known at run time (for example a generic function)
stops the program with a runtime error when it returns a non-Bool value.
Integer sum_list and stream sum reject an overflowing intermediate total.
Integer abs also fails if its result is outside the signed 64-bit range.
See integer arithmetic for ordinary and calculation error
handling.
= xs = [5, 2, 8, 1, 9, 3]
= sorted = sort(xs) -- [1, 2, 3, 5, 8, 9]
= words = sort(["banana", "apple", "cherry"]) -- ["apple", "banana", "cherry"]
-- Note: sort is lexicographic. sort([10, 2, 3]) gives [10, 2, 3] because "10" < "2".
-- Use sort_by for numeric sorting: sort_by([10, 2, 3], |x| x) sorts numerically.
= has_big = any(xs, |x| x > 7) -- true
= all_pos = all(xs, |x| x > 0) -- true
= found = find(xs, |x| x > 4) -- Some(5)
= total = sum_list(xs) -- 28
= uniq = distinct([1, 2, 2, 3, 1]) -- [1, 2, 3]
= evens = count_by(xs, |x| x % 2 == 0) -- 2
= pairs = zip(["a", "b"], [1, 2]) -- [("a", 1), ("b", 2)]
= indexed = enumerate(["x", "y", "z"]) -- [(0, "x"), (1, "y"), (2, "z")]
= prefix = take_while(xs, |x| x > 1) -- [5, 2, 8]
= chunks = chunked([1,2,3,4,5], 2) -- [[1,2], [3,4], [5]]
= halves = partition(xs, |x| x > 4) -- ([5, 8, 9], [2, 1, 3])
Tuple Accessors
| Function | Signature | Description |
|---|---|---|
fst | (a, b) -> a | First element of a tuple/pair |
snd | (a, b) -> b | Second element of a tuple/pair |
trd | (a, b, c) -> c | Third element of a tuple |
= p = (10, "hello")
= x = fst(p) -- 10
= y = snd(p) -- "hello"
= r = (10, "hello", true)
= z = trd(r) -- true
Map Operations
| Function | Signature | Description |
|---|---|---|
map_new | () -> Map(k, v) | Create empty map |
map_insert | (Map(k, v), k, v) -> Map(k, v) | Return new map with key→value added |
map_get | (Map(k, v), k) -> Option(v) | Lookup key, return Some(v) or None |
map_get_or | (Map(k, v), k, v) -> v | Lookup key with default if missing |
map_contains | (Map(k, v), k) -> Bool | Check if key exists |
map_remove | (Map(k, v), k) -> Map(k, v) | Return new map without key |
map_keys | Map(k, v) -> List(k) | All keys as a list |
map_values | Map(k, v) -> List(v) | All values as a list |
map_entries | Map(k, v) -> List((k, v)) | All entries as list of tuples |
map_len | Map(k, v) -> Int | Number of entries |
map_merge | (Map(k, v), Map(k, v)) -> Map(k, v) | Merge maps (second overwrites first) |
map_from | List((k, v)) -> Map(k, v) | Create map from list of tuples |
= m = map_new()
= m = map_insert(m, "name", "Alice")
= m = map_insert(m, "age", "30")
@ print(map_get_or(m, "name", "?")) -- Alice
@ print(map_contains(m, "age")) -- true
@ print(map_len(m)) -- 2
= keys = map_keys(m) -- ["age", "name"]
Map keys iterate in value order. Map keys and Set elements compare by value
and must not be or contain Float (check error: use Int, e.g. cents, or
String).
Set Operations
| Function | Signature | Description |
|---|---|---|
set_new | () -> Set(a) | Create empty set |
set_insert | (Set(a), a) -> Set(a) | Return new set with value added (dedup) |
set_contains | (Set(a), a) -> Bool | Check membership |
set_remove | (Set(a), a) -> Set(a) | Return new set without value |
set_len | Set(a) -> Int | Number of elements |
set_to_list | Set(a) -> List(a) | Convert to list |
set_union | (Set(a), Set(a)) -> Set(a) | Union of two sets |
set_intersect | (Set(a), Set(a)) -> Set(a) | Intersection |
set_diff | (Set(a), Set(a)) -> Set(a) | Difference (first minus second) |
set_from_list | List(a) -> Set(a) | Create set from list (deduplicates) |
= s = set_from_list(["red", "green", "blue", "red"])
@ print(set_len(s)) -- 3
@ print(set_contains(s, "green")) -- true
= s2 = set_insert(s, "yellow") -- 4 elements
= colors = set_to_list(s2) -- list of all colors
Stream Operators
These operators work on reactive streams (declared with ~). They complement the core stream operators (map, filter, scan, merge, zip, etc.) documented in streams.md.
| Function | Signature | Description |
|---|---|---|
tap | (Stream(a), a -> ()) -> Stream(a) | Side-effect observation: calls fn for each element, returns stream unchanged |
catch | (Stream(a), String -> Stream(a)) -> Stream(a) | When the stream ends with an error, continue with the recovery stream |
first | Stream(a) -> a | First element; raises first: empty list when empty |
reduce | (Stream(a), b, (b, a) -> b) -> b | Terminal fold: reduce stream to a single value |
start_with | (Stream(a), a) -> Stream(a) | Prepend a value to the front of a stream |
concat | (Stream(a), Stream(a)) -> Stream(a) | Concatenate two streams sequentially |
pairwise | Stream(a) -> Stream((a, a)) | Emit consecutive pairs (finite streams): [1,2,3] becomes [(1,2),(2,3)] |
~ nums = from_list([1, 2, 3, 4, 5])
~ observed = nums |> tap(|x| @ print("saw: " + show(x))) -- prints each, returns stream
= head = nums |> first -- 1
= total = nums |> reduce(0, |acc, x| acc + x) -- 15
~ prefixed = nums |> start_with(0) -- [0, 1, 2, 3, 4, 5]
~ both = concat(from_list([1, 2]), from_list([3, 4])) -- [1, 2, 3, 4]
~ pairs = nums |> pairwise -- [(1,2), (2,3), (3,4), (4,5)]
~ firsts = pairs |> map(|p| fst(p)) -- [1, 2, 3, 4]
Option / Result
| Function | Signature | Description |
|---|---|---|
unwrap_or | (Option(a), a) -> a | Unwrap with default |
is_some | Option(a) -> Bool | True if Some |
is_none | Option(a) -> Bool | True if None |
= val = unwrap_or(Some(42), 0) -- 42
= val2 = unwrap_or(None, 0) -- 0
= check = is_some(Some("hi")) -- true
For error propagation, use monadic bind:
> safe_divide(a: Int, b: Int) -> Result(Int, String) {
if b == 0 { Err("division by zero") }
else { Ok(a / b) }
}
> compute() -> Result(Int, String) {
= x <- safe_divide(10, 2) -- unwraps Ok, early-returns Err
= y <- safe_divide(x, 0) -- early-returns Err("division by zero")
Ok(x + y)
}
Logic
| Function | Signature | Description |
|---|---|---|
not | Bool -> Bool | Logical NOT |
assert | Bool -> () | Runtime assertion (panics on false) |
assert_with_message | (Bool, String) -> () | Runtime assertion with an explanatory failure message |
identity | a -> a | Identity function |
= flag = not(true) -- false
assert(2 + 2 == 4) -- passes
= x = identity(42) -- 42
assert_with_message(condition, message) evaluates both arguments in order,
including the message on success, and returns unit when the condition is true.
On false, ordinary interpreted/native execution stops with Assertion failed:
followed by the supplied text. The text is data, not a format string. Inside
runa call, failure becomes a case diagnostic with no result for that case;
other cases remain independent. Avoid personal data in messages. This is a
runtime check, not a proof or a replacement for a typed domain result when
the caller should handle alternative outcomes.
File I/O
File operations are invoked with the @ rune (effect boundary).
| Function | Signature | Description |
|---|---|---|
read_file | String -> Result(String, String) | Read entire file contents; Err when the file cannot be read |
write_file | (String, String) -> () | Write/overwrite file; a failed write stops the program with an error |
append_file | (String, String) -> () | Append to file; a failed write stops the program with an error |
file_exists | String -> Bool | Check if file exists |
read_lines | String -> List(String) | Read file as list of lines |
env_var | String -> String | Read environment variable |
@ write_file("output.txt", "hello world")
= exists = file_exists("output.txt") -- true
= content = read_file("output.txt") -- Ok("hello world")
= lines = read_lines("output.txt") -- ["hello world"]
@ append_file("output.txt", "\nline 2")
= home = env_var("HOME") -- "/Users/..."
Process Execution
Process execution is argv-based, not shell-based.
| Function | Signature | Description |
|---|---|---|
process_run | List(String) -> (Int, String, String) | Run argv without a shell, returning exit code, stdout, stderr |
= result = process_run(["git", "status", "--short"])
= code = result.0
= out = result.1
= err = result.2
process_run does not invoke a shell. Pass the executable and each argument as separate list elements.
JSON
JSON values are represented as String (serialized JSON text). Auto-adds serde_json dependency on first use.
| Function | Signature | Description |
|---|---|---|
json_parse | String -> Result(String, String) | Validate JSON; Ok returns the text, Err describes invalid input |
json_get | (String, String) -> String | Access object field (returns JSON text) |
json_string | String -> String | Extract string value (unquoted) |
json_number | String -> Float | Extract number |
json_bool | String -> Bool | Extract boolean |
json_array | String -> List(String) | Extract array elements as JSON strings |
json_emit | String -> String | Pass through (identity for JSON) |
json_object | List(List(String)) -> String | Build JSON from key-value pairs |
json_object escapes keys as JSON strings. Each value is parsed as complete
JSON text when valid; otherwise it becomes an escaped JSON string. For example,
"true" supplies a Boolean, while "\"true\"" supplies the string true.
Text such as "[draft]", "NaN", and "+5" remains text. Repeated keys retain
the last value. Serialized object key order is not input order.
= raw = "{\"name\": \"Alice\", \"age\": 30, \"tags\": [\"dev\", \"runa\"]}"
= parsed = json_parse(raw)
= name = json_string(json_get(parsed, "name")) -- "Alice"
= age = json_number(json_get(parsed, "age")) -- 30.0
= tags = json_array(json_get(parsed, "tags")) -- ["\"dev\"", "\"runa\""]
-- Build JSON
= obj = json_object([["city", "\"Copenhagen\""], ["temp", "22"]])
-- {"city":"Copenhagen","temp":22}
HTTP
HTTP client and server. Auto-adds ureq (client) and tiny_http (server) dependencies.
| Function | Signature | Description |
|---|---|---|
http_get | String -> Result(String, String) | GET request; Ok body for a 2xx response, Err for other statuses, connection failures and a 30 s timeout |
http_post | (String, String) -> Result(String, String) | POST with body; non-2xx, connection errors and timeouts are Err |
http_serve | (Int, (String, String, String) -> (Int, String, String)) -> () | Serve HTTP on the port until the process stops; compiled code only |
http_respond | (Int, String, String) -> (Int, String, String) | Build response tuple |
http_request_path | Request -> String | Extract request URL path |
http_request_method | Request -> String | Extract HTTP method |
http_request_body | Request -> String | Extract request body |
Client
= body = http_get("https://httpbin.org/get")
@ print(match body { | Ok(text) -> text | Err(e) -> "failed: " + e })
= response = http_post("https://httpbin.org/post", "{\"key\": \"value\"}")
@ print(match response { | Ok(text) -> text | Err(e) -> "failed: " + e })
Server
@ http_serve(8080, |path, method, body| {
if path == "/hello" {
http_respond(200, "text/plain", "Hello from Futuruna!")
} else {
http_respond(404, "text/plain", "Not found")
}
})
The handler receives three string arguments: request path, HTTP method, and request body. Return a response tuple via http_respond(status, content_type, body).
http_serve runs only in compiled code (runa run, runa build). The interpreter refuses it: reaching an http_serve call is a runtime error at that point.
Concurrency
| Function | Signature | Description |
|---|---|---|
spawn | (Actor, a) -> ActorHandle | Create actor with initial state |
ask | (ActorHandle, Msg) -> a | Handle the message and return the actor's new state |
shared | a -> shared(a) | Wrap value in Arc for thread-safe sharing |
Actors are defined with > actor, messages sent with <-:
> actor counter(state: Int) {
| Increment -> state + 1
| Decrement -> state - 1
| Reset -> 0
}
= c = spawn(counter, 0)
c <- Increment
c <- Increment
= val = ask(c, Increment)
@ print(show(val)) -- 3
Comptime
| Function | Signature | Description |
|---|---|---|
struct_type | List(Field) -> TypeDef | Generate struct type at compile time |
enum_type | List(String) -> TypeDef | Generate an enum with nullary variants at compile time |
field | (String, String) -> Field | Build a field descriptor |
@ comptime
= MyPoint = struct_type([field("x", "Float"), field("y", "Float")])
@ comptime
= Color = enum_type(["Red", "Green", "Blue"])
The binding supplies the generated type's name. These functions generate real Rust structs and enums at compile time; the annotation belongs on its own line before each binding.