Calling C
Albite calls C through extern "C" declarations. These have no body — they
name a symbol that the linker resolves:
extern "C" fn tg_string_length(s: String) -> Nat
pub extern "C" fn tg_cstr_eq(a: Nat, b: Nat) -> Bool
Once declared, they are called like any other function, and the type checker holds you to the signature you wrote.
Like everything else, an extern declaration is private to its module unless
marked pub.
What this is for
The FFI is how Albite reaches its runtime. String operations, allocation, and
the primitives underpinning the built-in types are C functions in
albite_core, declared this way and linked in. The same mechanism is
available to your own code for calling an existing C library.
The caveat worth stating
The signature you write is a promise the compiler cannot check. Nothing
verifies that tg_string_length really takes a string and returns a natural
number — if the C side disagrees, you get undefined behaviour rather than a type
error.
This is the one place where Albite’s guarantees stop. Everything the type checker tells you about pure Albite code rests on the kernel; everything across an FFI boundary rests on you having declared it correctly. Keep the surface small and concentrated in one module, so that the code needing that kind of care is easy to find.
Building strings: StringBuilder
Since 2.0-alpha — the runtime’s string builder, reached through
extern "C".
++ copies both of its operands into a new string, so building a string from
many pieces with it is quadratic. The runtime carries a growable buffer for
that job. It is not a built-in type: it is six C functions that every
natively compiled program links, and you reach them the way this page describes
— declare them, then wrap them. This is tests/golden/check/docs_string_builder.tg
in the compiler repository:
// `StringBuilder` is not a builtin: it is six runtime functions every
// natively compiled program links, declared with `extern "C"` and wrapped.
pub type StringBuilder = MkStringBuilder(Nat)
extern "C" fn tg_string_builder_new() -> Nat
extern "C" fn tg_string_builder_with_capacity(capacity: Nat) -> Nat
extern "C" fn tg_string_builder_push_str(sb: Nat, s: String) -> Nat
extern "C" fn tg_string_builder_push_char(sb: Nat, code: Nat) -> Nat
extern "C" fn tg_string_builder_len(sb: Nat) -> Nat
extern "C" fn tg_string_builder_to_string(sb: Nat) -> String
pub fn string_builder_new() -> StringBuilder {
MkStringBuilder(tg_string_builder_new())
}
pub fn push_str(sb: StringBuilder, s: String) -> StringBuilder {
match sb { MkStringBuilder(h) => MkStringBuilder(tg_string_builder_push_str(h, s)) }
}
// Consumes the builder: any later use of it aborts.
pub fn to_string(sb: StringBuilder) -> String {
match sb { MkStringBuilder(h) => tg_string_builder_to_string(h) }
}
type Words = NoWords | Word(String, Words)
fn push_all(sb: StringBuilder, ws: Words) -> StringBuilder {
match ws {
NoWords => sb,
Word(w, rest) => push_all(push_str(sb, w), rest),
}
}
fn main() -> String {
let words = Word("build ", Word("once, ", Word("copy never", NoWords)));
to_string(push_all(string_builder_new(), words))
}
albite run prints "build once, copy never"; the compiled binary prints it
unquoted. The four functions the example does not wrap —
with_capacity(bytes), push_char(code point) and len (in bytes) — follow
the same pattern.
What the wrapper is doing, and why:
- The handle is wrapped in a one-constructor type, not left as a
Nat, so that arithmetic on it is a type error. - Every push returns the same builder, so a builder threads through a fold
or a recursive function as an accumulator, as
push_alldoes. to_stringconsumes the builder. It hands the buffer over as theStringwithout copying, and the builder is dead afterwards: any later call on it prints one line naming it and aborts the program.- When to reach for it: whenever a string is assembled from more than a
handful of pieces. Measured on 15,000 pushes of a 44-byte piece, the builder
was 384 times faster than the same loop over
++.
The compiler’s own copy of this wrapper is src/compiler/driver/ffi/builder/mod.tg.