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)  intstring {
    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
fietAsynchronous finitepromissum<T> or promissum<T ⇥ E>
fiuntSynchronous stream (yields via cede)cursor values, optionally ⇥ E
fientAsynchronous stream (yields via cede)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
figendum T x ← futureAwait-bind immutable
variandum T x ← futureAwait-bind mutable
reddet futureAwait-return (fiet functions only)
tacebit futureAwait and discard
cede valueYield a value (fiunt / fient only)
fn responde() async  int {
    return 42
}

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

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

The @ futura and @ cursor annotations remain accepted compatibility spellings, but the posture words above are the canonical surface — prefer fiet over @ futura, and fiunt / fient over @ cursor.

Two-channel promises#

A fiet function returns a promissum — but the promise carries both channels, not just the eventual value. promissum<T> is the infallible form: shorthand for promissum<T ⇥ numquam>. promissum<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  intstring {
    if densitas ≺ 0 {
        throw "invalid input"
    }
    return 7
}

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

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

Promises in streams#

The two-channel shape composes with generators. An asynchronous stream may declare fient → 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 itera ex handles the channel:

fn poll() async_generator  intstring {
    yield 1
    throw "link lost"
}

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

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

fn stream() generator  intstring {
    yield 1
}

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

Borrowing and mutability (de, in, ex)#

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
fromConsume (move into callee)T by move
# Shared borrow
functio imprime(de textus label)  vacuum {
    nota label
}

# Mutable borrow
functio duplica(in numerus value)  vacuum {
    value  value * 2
}

# Consume
functio consume(ex textus buffer)  textus {
    redde buffer
}

# Owned
functio salve(textus nomen)  textus {
    redde "Salve, §!"(nomen)
}

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

SurfaceRole
de textus name on parameterShared borrow
in numerus count on parameterMutable borrow
ex textus buffer on parameterMove into callee
itera ex items fixum itemIterate values
itera de tabula fixum keyIterate keys
ex source fixum x, ceteri restDestructure fields
importa ex "path"Import from module

Entry point#

The program entry point is main:

main {
    print "ingressus"
}

incipiet is the async entry point — the body may await (figendum, variandum, reddet, tacebit) and call fiet / fient functions.

CLI entry point#

For CLI programs, incipit argumenta receives parsed command arguments:

@ cli "echo"
@ descriptio "Prints text"
@ operandus ceteri textus words
incipit argumenta args {
    itera ex args.words fixum word {
        nota word
    }
}

Passing mode — sponte#

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

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

Control flow#

Conditional branching#

si / sin / secus#

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

With else-if and else:

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

Compact branch with ergo#

A single-statement branch body uses ergo:

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

Iteration#

Values — itera ex#

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

Keys — itera de#

main {
    const json tabula  { "unus": 1, "duo": 2 }
    for ref tabula const key {
        print key
    }
}

Range — itera ab#

main {
    for range 010 const i {
        print i
    }
}

While loops#

main {
    const bool condition  true
    while condition {
        # body
        pass
    }
}

Guard sections — custodi#

custodi 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
}

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

Pattern matching — elige#

switch selects the first matching arm:

fn describe(int value)  string {
    match value {
        case 1 {
            return "one"
        }
        case 2 {
            return "two"
        }
        case _ {
            return "many"
        }
    }
}

Tagged union matching — discerne#

match exhaustively matches discretio 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 — fac / cape#

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 implendum accept type parameters with <T> syntax.

Generic functions#

fn identitas<T>(T valor)  T {
    return valor
}

fn primum<T>(list<T> res)  T  null {
    return res.primus()
}

Explicit call-site type arguments#

fn identitas<T>(T valor)  T {
    return valor
}

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

const int seven  identitas<int>(7)

const int  null maybe  primum<int>([seven])

Generic genus#

class Par<T> {
    T primus
    T secundus
}

Size parameters#

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

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