Renderingen-US

Functions and control flow

Functions#

Functions in Faber are declared with fn, using type-first parameter syntax and a glyph return type.

Basic syntax#

fn twice(int n) → int {
    return n
}

With an error channel:

fn parse(string input) → int ⇥ string {
    return 0
}

Examples#

# No parameters, no return
fn saluta() → void {
    print "Salve, Mundus!"
}

# Parameter, no explicit return
fn dic(string verbum) → void {
    print verbum
}

# Parameter and return type
fn duplica(int n) → int {
    return n * 2
}

# Multiple parameters
fn adde(int a, int b) → int {
    return a + b
}

Return values#

Use return for normal returns:

fn porta(int x) → int {
    if x ≺ 0 then return 0
    return x * 2
}

Bare return for void return type:

fn tace() → void {
    return
}

Async and streams#

Callable posture is a signature slot after modifiers and before → / ⇥ or the body. A bare function is synchronous finite; the posture words declare the execution mode:

PostureMeaningTypical return
(none)Synchronous finiteT
asyncAsynchronous finitepromise<T> or promise<T ⇥ E>
generatorSynchronous stream (yields via yield)cursor values, optionally ⇥ E
async_generatorAsynchronous stream (yields via yield)async cursor values, optionally ⇥ E
# Async finite — returns a promise
fn responde() async → int {
    return 42
}

# Synchronous stream — yields values
fn stream() generator → int {
    yield 1
    yield 2
}

Await forms bind or consume the eventual value:

FormRole
await_const T x ← futureAwait-bind immutable
await_var T x ← futureAwait-bind mutable
return_await futureAwait-return (async functions only)
await futureAwait and discard
yield valueYield a value (generator / async_generator only)
fn responde() async → int {
    return 42
}

async_main {
    await_const int responsum ← responde()
    await responde()
    print "done"
}

promise<T> is infallible shorthand for promise<T ⇥ numquam>; promise<T ⇥ E> preserves an alternate error channel. Infallible widens to failable; failable does not narrow.

The @ future and @ cursor annotations remain accepted compatibility spellings, but the posture words above are the canonical surface — prefer async over @ future, and generator / async_generator over @ cursor.

Two-channel promises#

A async function returns a promise — but the promise carries both channels, not just the eventual value. promise<T> is the infallible form: shorthand for promise<T ⇥ numquam>. promise<T ⇥ E> keeps the delayed alternate channel alongside the success value, so a failable async call fails exactly like a failable sync call — the error is delivered with the result, not through a separate callback, channel, or thrown exception. Awaiting a failable promise is itself a failable operation, so it happens inside a do / catch boundary:

fn computa(int densitas) async → int ⇥ string {
    if densitas ≺ 0 {
        throw "invalid input"
    }
    return 7
}

async_main {
    do {
        await_const int value ← computa(3)
        print value
    }
    catch err {
        print err
    }
}

The await forms bind or consume both channels: await_const / await_var await-bind the success value, return_await re-emits the promise from an async function, and await awaits and discards either outcome.

Promises in streams#

The two-channel shape composes with generators. An asynchronous stream may declare async_generator → T ⇥ E: every pull is itself a promise that either yields T, ends, or fails with E, and the first failure ends the stream. Iteration with for from handles the channel:

fn poll() async_generator → int ⇥ string {
    yield 1
    throw "link lost"
}

async_main {
    for from poll() const lectio {
        print lectio
    }
}

A synchronous stream may also carry an alternate channel (generator → T ⇥ E); the stream call is then failable, so the consuming code handles it with do / catch:

fn stream() generator → int ⇥ string {
    yield 1
}

main {
    do {
        for from stream() const item {
            print item
        }
    }
    catch err {
        print err
    }
}

Borrowing and mutability (ref, mut, from)#

Faber marks how a value is passed with short prepositions on parameters:

MarkerIntentTypical Rust lowering
(none)Owned valueT by value
refShared borrow (read-only)&T
mutMutable borrow&mut T
ownConsume (move into callee)T by move
# Shared borrow
fn imprime(ref string label) → void {
    print label
}

# Mutable borrow
fn duplica(mut int value) → void {
    value ← value * 2
}

# Consume
fn consume(own string buffer) → string {
    return buffer
}

# Owned
fn salve(string name) → string {
    return "Salve, §!"(name)
}

The same words (ref, from) are reused in other constructs — do not read every from as "consume":

SurfaceRole
ref string name on parameterShared borrow
mut int count on parameterMutable borrow
own string buffer on parameterMove into callee
for from items const itemIterate values
for ref map const keyIterate keys
from source const x, rest restDestructure fields
import from "path"Import from module

Entry point#

The program entry point is main:

main {
    print "ingressus"
}

async_main is the async entry point — the body may await (await_const, await_var, return_await, await) and call async / async_generator functions.

CLI entry point#

For CLI programs, main args receives parsed command arguments:

@ cli { name = "echo" }
@ description "Prints text"
@ operand { rest = true, type = string, binding = words }
main args args {
    for from args.words const word {
        print word
    }
}

Passing mode — optional#

optional marks a parameter that may be omitted by the caller:

fn connect(string host, int port optional) → void {
    print host
}

Control flow#

Conditional branching#

if / elif / else#

main {
    const _ condition ← true
    if condition {
        # truthy branch
        print "matched"
    }
}

With else-if and else:

main {
    const _ score ← 85
    if score ≥ 90 {
        print "A"
    }
    elif score ≥ 80 {
        print "B"
    }
    else {
        print "C"
    }
}

Compact branch with then#

A single-statement branch body uses then:

fn classify(int b, bool ready, int value) → int ∪ none {
    if b ≡ 0 then return null
    if ready then return value
    return null
}

Iteration#

Values — for from#

fn inveni(list<int> items, int target) → int ∪ none {
    for from items const item {
        if item ≡ target then return item
    }
    return null
}

Keys — for ref#

main {
    const map<string, int> map ← { "unus": 1, "duo": 2 }
    for ref map const key {
        print key
    }
}

Range — for range#

for range 0‥10 const i {
    print i
}

While loops#

main {
    const _ condition ← true
    while condition {
        # body
        pass
    }
}

Guard sections — guard#

guard groups early-exit checks before a function's main body. Each if clause is a sequential guard:

fn divide(int a, int b) → int {
    guard {
        if b ≡ 0 {
            return 0
        }
    }
    return a / b
}

guard is not breakable in v1 — it is a guard rail, not a loop.

Pattern matching — switch#

switch selects the first matching arm:

fn describe(int value) → string {
    switch value {
        case 1 {
            return "one"
        }
        case 2 {
            return "two"
        }
        default {
            return "many"
        }
    }
}

Tagged union matching — match#

match exhaustively matches union variants:

union Exitus {
    Bonum {
        string nuntius
    },
    Malum {
        string causa
    }
}

fn refer(Exitus eventus) → string {
    match eventus {
        case Bonum const nuntius {
            return nuntius
        }
        case Malum const causa {
            return "Error: §"(causa)
        }
    }
}

Try blocks — do / catch#

do opens a block that may throw, and catch recovers:

fn divide(int a, int b) → int {
    return a / b
}

fn tutus(int a, int b) → int {
    do {
        return divide(a, b)
    }
    catch err {
        warn err
        return 0
    }
}

Generics#

Functions, type aliases, class, and interface accept type parameters with <T> syntax.

Generic functions#

fn identitas<T>(T value) → T {
    return value
}

fn primum<T>(list<T> res) → T ∪ none {
    return res.first()
}

Explicit call-site type arguments#

fn identitas<T>(T value) → T {
    return value
}

fn primum<T>(list<T> res) → T ∪ none {
    return null
}

main {
    const _ seven ← identitas<int>(7)
    const _ maybe ← primum<int>([seven])
}

Generic class#

class Par<T> {
    const T primus
    const T secundus
}

Size parameters#

size declares a size/index parameter in generic parameter lists:

fn crea<T, size N>() → tensor<T, [N]> {
    return empty
}