Renderingen-US

Types and values

Data types#

Faber has a static, type-first type system. Every declaration places the type before the name — the string comes first, then the identifier it names, not the other way round. The type system covers scalar primitives, generic collections, sized numerics, tensors, and GPU-facing register types.

Primitive types#

TypeRoleExample literal
stringUnicode string"Salve, munde"
asciiFixed machine token'solum:lege'
intSigned integer (default i64)42
floatFloating-point (default f64)3.14
boolBooleantrue, false
voidUnit / no value
nullNull / absentnull
instantDuration / time instant
jsonCompile-time JSON value{ "key": "value" }
bytesHex byte sequence\|00ff\|

Sized numeric types#

int and float have default widths (i64 and f64) and explicit width forms:

const int<i32> narrow  7int<i32>

const int<u64> wide  255int<u64>

const f32 single  1.5 ∷ f32

Width sugar is available in type position: i8u64, f16, f32, f64 are equivalent to numerus<W> / fractus<W>.

Nullable types#

Nullable values use the union syntax T ∪ nihil:

fn find(string key)  int  null {
    return null
}

fn maybe()  string  null {
    return null
}

There is no T? or Option<T> syntax in Faber. The union is explicit.

Type aliases#

type UserId = int

Generics#

Functions, type aliases, class, and implendum accept type parameters with <T> syntax:

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

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

Explicit call-site type arguments are supported:

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

const int seven  identitas<int>(7)

Collections#

TypeRoleSugar
lista<T>Ordered dynamic collectionlf32, lu32
tabula<K, V>Key-value map
tensor<T, Figura>Dense fixed-shape buffertf32[4], ti64[2,3]
sparsa<T, Figura>Sparse fixed-shape buffersf32[4], si64[2,3]
intervallumRange type
copia<T>Unordered set
cursor<T>Lazy stream
promissum<T>Async finite result from fiet functions; promissum<T ⇥ E> carries a delayed alternate channel
const list<int> nums  [1, 2, 3]

const map<string, int> scores  { "alice": 10, "bob": 20 }

Tensor types#

tensor<T, Figura> is the dense fixed-shape container:

FormMeaning
tensor<T, Figura>Canonical spelling
tensor<T, []>Rank-0 (scalar container)
tensor<T, _>Shape inference hole
tensor<T, [N]>Rank-1 vector
tensor<T, [N, M]>Rank-2 matrix
const tensor<f32, []> scalar  vacua

const tensor<int, [4]> row  [1, 2, 3, 4] ↦ tensor<int, [4]>

const int  null first  row[0]

GPU core types#

These are recognised by the systems lane for GPU and register work. Package targets that lack hardware support reject them:

fn half(f16 x)  f16 {
    return x
}

fn add(matrix<f32, [2, 2]> a, matrix<f32, [2, 2]> b)  matrix<f32, [2, 2]> {
    return a.addita(b)
}

fn swap(atomic<int<i32>> cell, int<i32> value)  int<i32> {
    return cell.exchange(value)
}

Borrow markers on types#

Borrow markers (ref, mut, from) can appear on types in parameter positions to indicate how a value is passed:

# shared borrow — caller retains ownership
functio imprime(de textus label)  vacuum { }

# mutable borrow — caller lends mutable access
functio duplica(in numerus value)  vacuum { }

# move — caller gives up ownership
functio consume(ex textus buffer)  textus {
    redde buffer
}

Comparison policy#

OperatorFamilyBehaviour
, Exact equalityIdentical types required; null bypass
, Numeric value equalityNumeric lattice only
<, , >, OrderingNumeric, instant, scalar text
intraRange containmentNumeric in range
interCollection membershipElement in collection

Variables and binding#

Faber has three variable keywords and a dedicated assignment glyph. The key distinction is between const (write-once) and var (freely reassignable), and between (runtime flow) and = (structural field shape).

fixum — immutable binding#

const bindings are write-once. They may be declared with or without an initializer; if declared without, they must be assigned exactly once before reading. A second assignment is rejected.

const int count  0

const string name  "Marcus"

const list<int> inferred  [1, 2, 3]

Deferred initialisation:

main {
    const int factor
    if true {
        factor  10
    }
    else {
        factor  100
    }
    print factor
}

varia — mutable binding#

var bindings are freely reassignable:

main {
    var int count  0
    count  count + 1
    count  count * 2
}

sit — inferred immutable sugar#

sit is sugar for fixum _ — an immutable binding with inferred type:

main {
    const string salve  "Salve"
    const string nomen  "Marcus"
    const int x  42

    # Deferred form
    const string label
    label  "deferred"
}

Runtime binding vs structural definition#

Faber splits what most languages collapse into =:

GlyphRoleUse for
Runtime flowInitial binding, reassignment, mutation
=Structural shapeField names inside literals and metadata
class Point {
    int x
    int y
}

main {
    # Runtime: ← attaches a value to a name at execution time
    var int count  0
    var string label  "ready"
    count  count + 1

    # Structural: = defines field values inside a type literal
    const Point p  Point {x = 10, y = 20}
}

Ex field extraction#

from extracts fields from a value into local bindings:

class Persona {
    string nomen
    int aetas
}

main {
    const Persona p  Persona {nomen = "Marcus", aetas = 30}
    const string nomen  p.nomen
    const int aetas  p.aetas
    # prints "Marcus"
    print nomen
}

Postfix increment and decrement#

and are postfix increment/decrement statements for mutable int places. They are statement-only — no expression value, no prefix forms:

main {
    var int i  0
    # i becomes 1
    i 
    # i becomes 0
    i ⊖
}

Collections#

Faber has several compiler-owned collection types. Their canonical methods live in the compiler, not in the standard library.

Lista — ordered dynamic collection#

const list<int> empty  vacua

const list<int> numbers  [1, 2, 3, 4, 5]

const list<string> names  ["Marcus", "Julia", "Gaius"]

const list<list<int>> nested  [[1, 2], [3, 4]]

Spread with sparge:

const list<int> a  [1, 2, 3]

const list<int> b  [4, 5, 6]

const list<int> combined  [spread a, spread b]

const list<int> headed  [0, spread a, 99]

Key methods: longitudo, accipe, appende, summa, primus, novissimus.

Tabula — key-value map#

const map<string, int> scores  { "alice": 10, "bob": 20 }

The : there is not map syntax. A bare { … } is always inline JSON — a compile-time json document whose keys are quoted strings separated by :. Declaring the binding as a map ascribes that document to a map type, which lowers it to a real constant map.

Faber's own key-value shape uses =, and it is only available on a named type: Point { x = 10 }. There is no anonymous { key = expr } object — writing one is a parse error, not a second spelling of the line above.

For a map you build up rather than declare whole, start from vacua and assign by key:

main {
    var map<string, int> puncta  vacua
    puncta["alpha"]  1
    puncta["beta"]  2
    print puncta.longitudo()
}

Tensor — dense fixed-shape buffer#

const tensor<f32, []> scalar  vacua

const tensor<int, [4]> row  [1, 2, 3, 4] ↦ tensor<int, [4]>

const int  null first  row[0]

Tensor sugar (numeric-heavy code):

const tensor<f32, []> seed  vacua

const tensor<f32, [4]> lanes  seed.strue([1.0, 2.0, 3.0, 4.0], [4])

Key methods: forma, accipe, ponde, crea, structa, strue, plus elementwise arithmetic, matrix multiplication (multiplicatio), and reductions (summa, productum).

Sparsa — sparse fixed-shape buffer#

const sparsa<f32, [2, 3]> sparse  vacua

main {
    sparse.ponde([0, 1], 4.0)
    sparse.ponde([1, 2], 9.0)

    # accipe returns the stored value, here 4.0
    print sparse.accipe([0, 1])
    # count of stored entries
    print sparse.nonnihil()
}

Conversion between dense and sparse:

const tensor<f32, [2, 2]> dense  [[1.0, 0.0], [0.0, 2.0]] ↦ tensor<f32, [2, 2]>

const sparsa<f32, [2, 2]> sparse  dense ↦ sparsa<f32, [2, 2]>

const tensor<f32, [2, 2]> roundtrip  sparse ↦ tensor<f32, [2, 2]>

Cursors — lazy streams#

cursor<T> is a lazy stream type. Created from collection iterators, tuus views, or generator functions. Consumed via itera ex:

const list<int> items  [1, 2, 3]

main {
    for from items const item {
        print item
    }
}

Generator functions declare their stream posture in the signature slot: fiunt is a synchronous stream and fient an asynchronous stream; the body yields values with cede (see Functions — async and streams).

Intervallum — ranges#

main {
    # exclusive range: 0, 1, 2, 3, 4
    for range 05 const i {
        print i
    }
    # inclusive range: 0, 1, 2, 3, 4, 5
    for range 05 const i {
        print i
    }
}

is exclusive range endpoint; is inclusive.

String and template literals#

Faber uses delimiter semantics — each quote form means a different source shape. They are not interchangeable synonyms.

Literal forms#

FormTypeRole
'…'asciiFixed machine tokens; no §; no (…)
"…"stringShort Unicode line strings; (…) renders
«…»stringBlock/multiline Unicode; (…) renders
formaCaptured templates; (…) captures
{ … }jsonCompile-time JSON document
``bytesCompile-time hex bytes
[ … ]lista<T>Faber list literal

String-template application#

Faber formats text with string-template application: a "…" or «…» literal with § holes, then parenthesised arguments:

fn greet(string nomen)  string {
    return "Salve, §!"(nomen)
}

const int pagina  3

const int totum  10

const string code  "200"

const string label  "OK"

const string msg  "Page § of §"(pagina, totum)

const string block  "status: § (§)"(code, label)

Key rules:

  • § (U+00A7) is the template hole
  • Positional holes: §0, §1, … for explicit ordering
  • Trailing ! selects display formatting: "Salve, §!"(nomen)
  • The (args) suffix is template application, not a function call

Block strings#

Multiline blocks use guillemets «…»:

const string sql  «
    select id, email
    from accounts
»

Guillemets are the only block-string spelling since Radix v0.79.0 — the retired """ and ❝…❞ spellings fail as ordinary lex errors.

Captured templates (forma)#

Backtick templates capture text and parameters without rendering. Safe for bound SQL/URL payloads:

const int user_id  42

const forma query  `select * from users where id = §`(user_id)

Inline JSON#

A bare { … } is inline JSON: a compile-time json document, not an anonymous Faber object. Keys are quoted strings separated by :. Values are JSON constants only — no variable references, no Faber expressions. Ascribing one to a map lowers it to a real constant map; ↦ valor widens it to the dynamic carrier instead:

const json empty  {}

const json user  { "name": "Marcus", "age": 30, "active": true }

const json nested  { "meta": { "version": 1 }, "tags": ["alpha", "beta"] }

For typed genus construction, use the type name and = field shape:

class Point {
    int x
    int y
}

const Point p  Point {x = 10, y = 20}

Nullability and optionality#

Faber distinguishes absence in a value from optional provision at a declaration site.

Nullable values — T ∪ nihil#

Use T ∪ nihil when the value can be absent:

fn find(string key)  int  null {
    return null
}

fn divide(int a, int b)  int  null {
    if b  0 then return null
    return a / b
}

Optional declaration slots — sponte#

Use sponte after the name when a parameter or field may be omitted by the caller or constructor:

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

class User {
    string email optional
}

Borrow markers can combine with optional parameters:

fn process(ref int depth optional)  void {
}

Non-null assertion — !#

Use !., ![, !( to assert a nullable value is not null:

class Box {
    int  null val
}

const Box  null maybe_name  Box {val = 7}

const int  null name  maybe_name!.val

A non-null assertion on null aborts at runtime.

Nullish coalescing — vel#

const string  null provided  null

const string name  provided coalesce "default"

ignotum#

ignotum is the top-level unknown type for escape hatches and incomplete knowledge. It is not a nullability mechanism.

Conversion and construction#

Two important conversion operators, one for runtime and one for compile-time:

# runtime conversion
const int parsed  "42"int
# static ascription
const int count  7

const string text  count ∷ string

Runtime conversion — ↦#

Use for runtime conversion, especially parsing or coercion that may fail. Supply inline recovery with :

const string input  "9"

const int n  "42"int

const int safe  input ↦ int0

Type-directed materialization:

const string path  "/etc/hosts"

const vector<f32, 4> lanes  [1.0, 2.0, 3.0, 4.0] ↦ vector<f32, 4>

const string body  call 'solum:lege' (path) ↦ string

Static ascription — ∷#

Use for explicit static type ascription. It is postfix and target-type driven:

const int count  7

const int<i32> x  7int<i32>

const string text  count ∷ string

Nullish coalescing — vel#

Use vel for nullish coalescing when a value is null:

const string  null provided_name  null

const string name  provided_name coalesce "default"