# > | = ~ @ ?

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

FunctionSignatureDescription
showa -> StringConvert any value to its string representation
printString -> ()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:

ValueDisplay
String, Charthe text itself, also inside lists, records and streams (["v1.0", "a { b }"] → [v1.0, a { b }], 'c' → c)
Floatshortest 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, constructorName(field: value) for named fields, Name(a, b) for positional fields, Name without fields
Option, ResultSome(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

FunctionSignatureDescription
absInt -> IntAbsolute value
sqrtFloat -> FloatSquare root
pow(Float, Float) -> FloatExponentiation
expFloat -> FloatNatural exponential (e^x)
lnFloat -> FloatNatural logarithm
roundFloat -> IntRound to nearest integer
floorFloat -> IntFloor (round down)
to_floatInt -> FloatConvert integer to float
max_int(Int, Int) -> IntMaximum of two integers
min_int(Int, Int) -> IntMinimum of two integers
max_f(Float, Float) -> FloatMaximum of two floats
min_f(Float, Float) -> FloatMinimum of two floats
clamp(Int, Int, Int) -> IntClamp 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.

FunctionSignatureDescription
string_lengthString -> IntNumber of Unicode scalar values in string
split(String, String) -> List(String)Split by separator
join(List(String), String) -> StringJoin with separator
trimString -> StringRemove leading/trailing whitespace
contains(String, String) -> BoolSubstring test
starts_with(String, String) -> BoolPrefix test
ends_with(String, String) -> BoolSuffix test
replace(String, String, String) -> StringReplace all occurrences
to_upperString -> StringConvert to uppercase
to_lowerString -> StringConvert to lowercase
substring(String, Int, Int) -> StringExtract by scalar start index and scalar length
char_at(String, Int) -> StringSingle Unicode scalar value by index
index_of(String, String) -> IntFind substring scalar position (-1 if absent)
format_float(Float, Int) -> StringFormat float with N decimal places
parse_intString -> Result(Int, String)Parse an integer; Err for invalid text
parse_floatString -> Result(Float, String)Parse a finite number; Err for invalid text
string_charsString -> 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

FunctionSignatureDescription
lengthList(a) -> Int or String -> IntList elements or Unicode scalar values
headList(a) -> aFirst element
tailList(a) -> List(a)All but first
push(List(a), a) -> List(a)Append element
concat(List(a), List(a)) -> List(a)Concatenate two lists
reverseList(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) -> bLeft 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.

FunctionSignatureDescription
sortList(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) -> BoolTrue if any element matches
all(List(a), a -> Bool) -> BoolTrue 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
enumerateList(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_listList(Int) -> IntSum of integer list
distinctList(a) -> List(a)Remove duplicates (preserves order)
count_by(List(a), a -> Bool) -> IntCount 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

FunctionSignatureDescription
fst(a, b) -> aFirst element of a tuple/pair
snd(a, b) -> bSecond element of a tuple/pair
trd(a, b, c) -> cThird 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

FunctionSignatureDescription
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) -> vLookup key with default if missing
map_contains(Map(k, v), k) -> BoolCheck if key exists
map_remove(Map(k, v), k) -> Map(k, v)Return new map without key
map_keysMap(k, v) -> List(k)All keys as a list
map_valuesMap(k, v) -> List(v)All values as a list
map_entriesMap(k, v) -> List((k, v))All entries as list of tuples
map_lenMap(k, v) -> IntNumber of entries
map_merge(Map(k, v), Map(k, v)) -> Map(k, v)Merge maps (second overwrites first)
map_fromList((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

FunctionSignatureDescription
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) -> BoolCheck membership
set_remove(Set(a), a) -> Set(a)Return new set without value
set_lenSet(a) -> IntNumber of elements
set_to_listSet(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_listList(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.

FunctionSignatureDescription
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
firstStream(a) -> aFirst element; raises first: empty list when empty
reduce(Stream(a), b, (b, a) -> b) -> bTerminal 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
pairwiseStream(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

FunctionSignatureDescription
unwrap_or(Option(a), a) -> aUnwrap with default
is_someOption(a) -> BoolTrue if Some
is_noneOption(a) -> BoolTrue 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

FunctionSignatureDescription
notBool -> BoolLogical NOT
assertBool -> ()Runtime assertion (panics on false)
assert_with_message(Bool, String) -> ()Runtime assertion with an explanatory failure message
identitya -> aIdentity 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).

FunctionSignatureDescription
read_fileString -> 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_existsString -> BoolCheck if file exists
read_linesString -> List(String)Read file as list of lines
env_varString -> StringRead 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.

FunctionSignatureDescription
process_runList(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.

FunctionSignatureDescription
json_parseString -> Result(String, String)Validate JSON; Ok returns the text, Err describes invalid input
json_get(String, String) -> StringAccess object field (returns JSON text)
json_stringString -> StringExtract string value (unquoted)
json_numberString -> FloatExtract number
json_boolString -> BoolExtract boolean
json_arrayString -> List(String)Extract array elements as JSON strings
json_emitString -> StringPass through (identity for JSON)
json_objectList(List(String)) -> StringBuild 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.

FunctionSignatureDescription
http_getString -> 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_pathRequest -> StringExtract request URL path
http_request_methodRequest -> StringExtract HTTP method
http_request_bodyRequest -> StringExtract 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

FunctionSignatureDescription
spawn(Actor, a) -> ActorHandleCreate actor with initial state
ask(ActorHandle, Msg) -> aHandle the message and return the actor's new state
shareda -> 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

FunctionSignatureDescription
struct_typeList(Field) -> TypeDefGenerate struct type at compile time
enum_typeList(String) -> TypeDefGenerate an enum with nullary variants at compile time
field(String, String) -> FieldBuild 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.