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:
| Posture | Meaning | Typical return |
|---|---|---|
| (none) | Synchronous finite | T |
fiet | Asynchronous finite | promissum<T> or promissum<T ⇥ E> |
fiunt | Synchronous stream (yields via cede) | cursor values, optionally ⇥ E |
fient | Asynchronous 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:
| Form | Role |
|---|---|
figendum T x ← future | Await-bind immutable |
variandum T x ← future | Await-bind mutable |
reddet future | Await-return (fiet functions only) |
tacebit future | Await and discard |
cede value | Yield 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 → int ⇥ string {
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 → 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 (fiunt → 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 (de, in, ex)#
Faber marks how a value is passed with short prepositions on parameters:
| Marker | Intent | Typical Rust lowering |
|---|---|---|
| (none) | Owned value | T by value |
ref | Shared borrow (read-only) | &T |
mut | Mutable borrow | &mut T |
from | Consume (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":
| Surface | Role |
|---|---|
de textus name on parameter | Shared borrow |
in numerus count on parameter | Mutable borrow |
ex textus buffer on parameter | Move into callee |
itera ex items fixum item | Iterate values |
itera de tabula fixum key | Iterate keys |
ex source fixum x, ceteri rest | Destructure 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 0‥10 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
}