Standard Library by Task
Every .x translation unit receives the x2c runtime prelude. Each section
below names the type to use for one task. The generated
module reference lists every signature and its behavior,
grouped by API tier.
The implicit prelude is the standard library. Optional x2c system modules are
shipped, built, and tested with x2c, and need an explicit include. Runtime
internals are implementation details. Third-party libraries are under
packages/ and use import.
Hold a value whose type varies
Use Var when you do not know the type of a value until the program runs. Use the C type everywhere else.
Var value = 42;
value = "forty-two";
if (value is String)
printf("%s\n", value);
Dynamic arithmetic and truthiness are in
Var operations. Checked conversion is in
Var conversion. void means missing or exhausted,
and collections and iterators never carry it as a payload.
Choose a collection
| Need | Type | Important contract |
|---|---|---|
| persistent heterogeneous sequence | List | canonical and immutable |
| growing indexed sequence | Array | mutable object identity |
| growing keyed lookup | Map | mutable object identity |
| canonical byte text | String | interned, NUL-terminated |
| streaming traversal | Iter | caller-owned iterator storage |
List and String values are canonical. Array and Map are distinct
allocated objects even when empty.
Build and transform text
String does interpolation, search, split, replace, partition, case conversion, padding, escaping, slicing, joining, and checked numeric parsing. Its operations work on bytes; they do not decode Unicode.
Use Buffer for incremental text:
Buffer output = $auto(Buffer.new(0));
output.write("count=");
output.printf("%d", 4);
String text = output;
printf("%s\n", text);
Use Symbol for compact immediate tags and Atom for exact names that may be longer.
Read or write a file
File wraps C streams. Iterate a text file by line:
File input = tmpfile();
input.puts("first\nsecond\n");
input.rewind();
foreach(String line, input)
printf("%s", line);
input.close();
Use File.readline_into and File.read_into for raw bytes with separate
data, EOF, and error status. Use File.write_all and File.copy_to when a
partial write must be finished or reported.
Transform a sequence
Every collection supports iteration. Use foreach to walk one:
List values = %(1 2 3);
foreach(int value, values)
printf("%d\n", value);
Use Iter to process values as they are requested. Use a
collection’s map, filter, and fold operations to compute the result
immediately. Iter.try_next separates a value from exhaustion and never yields
void as data.
Match structured Lists
The language match statement and Match use the same
List patterns:
List event = %(ready 42);
match (event) {
case %(ready ?value):
printf("%ld\n", value);
}
Use the match statement for control flow and List.try_match or the other
runtime matching functions when the patterns are themselves data.
Own allocation lifetime
Scope groups runtime allocations:
$scope() {
String text = "temporary";
Array values = [text, 1, 2, 3];
printf("%s\n", values.repr());
}
Use Block for fixed-width growable storage and Buffer for
text construction. External resources such as Files still close explicitly or
through defer.
Each Scope.release must match a Scope.retain on the same active slot. See
scopes and lifetime.
Bound temporary work
Context combines a Scope lifetime with independent
Error, cleanup, and Match state. An isolated Context also gives temporary
String and List values private pool branches:
Context work = Context.open_isolated_named("request");
List temporary = %(answer 42);
List result = work.export(temporary);
work.close();
printf("%s\n", result.repr());
Closing reclaims everything that was not exported. Close Contexts in
last-opened, first-closed order. See
contexts and threads.
Run work on native threads
Thread runs a fixed-signature callback in an isolated
Context. It copies the input structure, but pointers inside that structure
still point into shared process memory. A worker can read parent Strings and
Lists directly. Use Mutex when a shared value may be
written:
typedef struct CounterWork {
Mutex mutex;
int *counter;
String label;
} CounterWork;
static Var increment(const void *input, size_t input_size) {
if (input_size != sizeof(CounterWork)) raise %(bad-arg);
const CounterWork *work = input;
$lock(work.mutex) (*work.counter)++;
return work.label;
}
int main(void) {
int counter = 0;
Mutex mutex = Mutex.new();
CounterWork work = { mutex, &counter, "parent label" };
Thread first = Thread.start(increment, &work, sizeof(work));
Thread second = Thread.start(increment, &work, sizeof(work));
String a = first.join();
String b = second.join();
printf("%s, %s: %d\n", a, b, counter);
first.free();
second.free();
mutex.free();
return 0;
}
Every started Thread needs one successful Thread.join, then
Thread.free. Contexts and threads
gives the sharing and result rules.
Handle absence and failure
These operations report absence separately from data:
Map.try_getandMap.try_delreport whether the key was present, separately from its value.Iter.try_nextseparates a value from exhaustion.String.try_longandString.try_doubleseparate zero from parse failure.- raw
Filereaders separate data, EOF, and error.
An operation that fails raises its cause through Error, and a filtered
catch or the cause’s policy handles it. Absence, exhaustion, and parse
results come back as return values.
Use Error transfer and cleanup
Language raise, try, filtered catch, finally, and defer are backed by
Error and the cleanup-frame runtime. Application code uses those statements
and leaves the runtime frame functions alone. See Errors and
Cleanup.
Bind native functions
Func binds an already-linked native function with a checked
signature. Construction, binding, conversion, and application failures raise
their cause through Error; none of those causes return to the call. A fixed
nonvariadic function or function-pointer value with a supported signature
converts implicitly where a Func is expected. Direct conversions reuse a
file-static binding that lives for the program, so an operation that takes
ownership of a Func, such as Lisp.bind, leaves it in place. Pointer
conversions snapshot the pointer in a binding held by the current Scope.
Call a C function directly when its name is known at compile time.
Log structured events
Logger does level filtering and writes to ordered stderr,
file, memory, and custom sinks. A category labels an event; it never suppresses
one. The global log_* functions are the simplest interface; use a Logger
object to configure logging and manage its lifetime.
Embed Lisp
Lisp provides a reader, evaluator, lexical closures, macros,
global bindings, native Func integration, host-side value application, and
file/string evaluation:
static String greet(String name) {
return %"hello $name";
}
int main(void) {
Lisp lisp = Lisp.new();
defer lisp.destroy();
$lisp.bind(lisp, "greet", greet);
String result = lisp.eval(%(greet "Ada"));
return result == "hello Ada" ? 0 : 1;
}
Lisp.new() creates an isolated session and loads etc/init.xlisp. That file
supplies defun, short-circuit control, higher-order List operations,
numeric comparisons, basic String operations, and the pattern matching
described in the language reference. Lisp.kernel() creates the
evaluator primitives alone, for hosts that build their own environment.
etc/lisp-values.xlisp binds the core List, Array, Map, String,
Var, and Symbol operations; the compiler loads it into every compile-time
session, and an embedding host loads it by name.
etc/lisp-io.xlisp stays optional and must be loaded by name. It adds
read-file, write-file, sort, fib, and subst.
Use $lisp.binding(group, "name") on several direct functions and
$lisp.install(lisp, group) when a unit has a reusable set. Functions must
appear before the install call, and names in one group must be unique.
Installing the same group into two sessions creates two sets of Func values,
and each session releases its own in Lisp.destroy().
A group compiles into the unit that declares it. A package can therefore publish its bindings behind one public installer that a consumer calls after importing it. See Wrapping a C Library.
$lisp.bind and grouped bindings accept the parameter and result types
Func supports, including untagged pointers and structs passed as <p48>
addresses. Native functions are trusted host code. A package-specific opaque
object needs its own Var adoption.
Load a Lisp program with Lisp.eval_file, then evaluate runtime List forms
with Lisp.eval. $ inside %(...) inserts live x2c values; it is unrelated
to compile-time $(...) Lisp. Within a List literal, ', `, ,, and
,@ write quote, quasiquote, unquote, and unquote-splicing just as
they do in Lisp source. A flat List result destructures through the same
typed conversions as any other value:
int main(void) {
Lisp lisp = Lisp.new();
defer lisp.destroy();
String role = "editor";
List outcome = lisp.eval(%(decide (quote ((role $role)))));
String decision;
int limit;
(decision, limit) = outcome;
return 0;
}
Look up an exact operation
The module reference is generated from runtime source and compiler-verified signatures.