Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

From C to x2c

This chapter lists where x2c syntax or meaning differs from C. Each section links to the chapter that explains it.

x2c translates to C and uses the native compiler, linker, object layout, and calling conventions. Scalars, aggregates, pointers, arrays, and ordinary functions remain useful representations. Each .x unit also loads the standard runtime prelude. Var, collections, protocols, and closures add operations where you use them. Memory ownership remains explicit: there is no garbage collector or reference counting.

Files, visibility, and the native build

Write or runMeaning
module.xA unit containing declarations and definitions; translation produces a C source and header
#include "module.x"Make that unit’s public declarations and compile-time definitions visible
#include "native.h"Read native declarations and preserve the native include in generated C
staticPrivate function/object; also permitted on a type declaration to control its publication
import "geo" as g with Vec;Import a registered package; use g.Vec or the selected bare name Vec
x2c translate --out-dir generated main.xLet an existing native build compile and link the output
x2c build --output app main.x helper.xLet x2c own compilation and linking; list the local implementation units
x2c run main.x -- argumentBuild and execute

Declarations are public by default. A type that a public declaration needs becomes public; its body is published only when the interface needs the layout. Including a local .x exposes its declarations, and the build must also link its implementation. See units and visibility and type-owned initialization.

A first-line #!/usr/bin/env -S x2c script makes a script unit, whose top-level statements form the program body. See scripting.

An .xp file, or #pragma indent before code, uses indentation in place of braces and semicolons. The parser then reads the same tokens as the brace form. See indentation syntax.

Types, methods, and references

A method is a function with a type-qualified name. Dotted calls select it from the receiver’s static type and pass the receiver as the first argument.

typedef struct Counter { int value; } Counter;

void Counter.add(Counter &counter, int amount) => counter.value += amount;
int Counter.get(Counter counter) => counter.value;

int main(void) {
Counter count = {0};
count.add(3);
printf("%d\n", count.get());
return 0;
}
SpellingRule
int T.f(T value, int n)Defines T.f; generated C uses a function such as T_f
object.fieldAlso works through one pointer level when x2c knows the aggregate layout; -> remains valid
T &value parameterBorrows the caller’s addressable T; call with value, not &value
T &?value parameterAlso accepts NULL; test presence before using the borrowed object
Self in a method signaturePreserves the original receiver’s static typedef at the marked parameter/result positions
delegate T field;Forwards otherwise unresolved dotted method calls through that field
result_type f(...) => expression;A one-expression function body; a void function executes it without returning a value

Reference parameters lower to C pointers and are allowed only on parameters. Use -> where x2c cannot see the aggregate definition. Some pointer mismatches that C only warns about are x2c errors. The C foundation gives the rules for references, delegation, typedef chains, and conversions.

What class and protocol mean

class is a shipped declaration macro. The declaration chooses an ordinary C representation and supplies applicable constructors, conversions, comparison, hashing, and printing defaults:

class Point { int x; int y; };
class Node { int value; } *;
class Count int;

int main(void) {
Point p = Point.new(3, 4);       // record value
Node n = Node.new(7);           // pointer; default allocation uses Scope
Point copy = p;                 // ordinary shallow record copy
n.free();
return 0;
}

Methods can replace the defaults. Copying a record with an Array or pointer field shares that backing object. See classes and system macros.

A protocol declares a compile-time contract. On adoption, the compiler resolves the protocol’s members and any adapters they need:

typedef struct Reading { int value; } Reading;
typedef struct Gauge { int value; } Gauge;
protocol Reading(T) {
  int T.value(T);
}

int Gauge.value(Gauge gauge) => gauge.value;
protocol Reading(Gauge);

A type participates only through an explicit adoption. Var(T) also connects a type to boxing and runtime operations. See protocols.

Runtime values and implicit operations

TypeRepresentation and consequence
Native C typeKeeps native storage and arithmetic unless an x2c conversion or protocol operation applies
VarEight-byte tagged value; supported values box/unbox at typed boundaries; wide scalars and some custom values require allocated boxes
StringImmutable canonical text; equal content shares storage
ListImmutable canonical cons structure; elements are Var values
Array, MapMutable objects with identity; copying their handles shares the object
SymbolEncoded 64-bit tag, written <ready>; source spelling must fit exactly
AtomExact name of arbitrary length, used by quoted data; compact names use Symbol representation
FuncCallable runtime handle, possibly with captures; calls through it return Var

Initializers, assignments, typed arguments, and returns can call converters: Var v = 42; boxes, and a typed destination unboxes. Some pairs have no converter. NULL, an empty String or List, and void are three different values, and a condition on a runtime value uses its truth operation, so an empty Array is false. See values and Var.

Literals: evaluated expressions versus quoted data

int n = 4;
Array evaluated = [n, n + 1];
List quoted = %(n $n ${n + 1});
Map settings = {mode: "fast", count: n};
String message = %"count=$n next=${n + 1}";

The Array contains 4, 5. The List contains the Atom n, then 4, 5. The Map’s bare keys are Atoms; {(name): value} uses a variable as its key.

FormContents
[expressions], {key: expression}Evaluate expressions; nested bare collections do likewise
%(items)Whitespace-separated List data; bare names are Atoms
%[items], %{key: value}Array/Map with quoted contents; commas separate entries
%"text $name ${expression}"Interpolated String
%<<ready busy done>>Immutable ordered SymbolSet; literal members only
$name, ${expression} inside quoted dataInsert one evaluated value
@name, @{expression} inside a quoted ListSplice a List’s elements; Arrays/Maps do not support this splice

Bare {} depends on its destination: it builds a Map or Array, or zero initializes a native object. Inside %(), commas and apostrophes are Lisp reader punctuation. After a cast’s closing ), %( reads as modulo, so write (List) (%(a b)). Numbers also accept 0o octal and 0b binary. See collections and the literal rules.

Operators, indexing, and small declaration additions

SpellingMeaning
a == b, a != bUses the type’s equality operation when one exists; two Var numbers compare as their C values do
a === b, a !== bIdentity tests; two Var numbers are identical with the same type and bits
value is Type, value is not TypeRequire a Var left operand; test its exact tag, not ancestry or convertibility; a tag Symbol may replace Type
item in collectionMembership; Maps test keys, Strings test substrings; negate with !(item in collection)
items[-1], items[start:stop:step]Negative indexing and slicing on Array/List/String; omitted bounds are allowed, zero step is invalid
int i, float x;Restart declaration specifiers after a comma
Var (a, b) = values;Destructure a List into new bindings
(int a, String b) = values;Destructure into separately typed new bindings
(a, b) = values;Destructure into existing simple identifiers
threadedSpelling of C thread storage; _Thread_local and thread_local also work

Other operators may call protocol members: String + concatenates, and String comparison uses content. The reference gives the rules for indexing, declaration rows, destructuring, and operator precedence.

Iteration, matching, and closures

List rows = %((add 2) (add 5));
foreach (List row in rows) {
  match (row) {
    case %(add ?(int amount)) if (amount > 0):
      printf("%d\n", amount);
    default: break;
  }
}

foreach evaluates its source once. match tries its arms in order: ?name binds one element, *rest binds the remaining elements, and ?(T name) also checks the tag. See iteration and pattern matching.

int total = 1;
Func snapshot = %!() => total;
Func add = %!(int n) using &total => total += n;
total = 10;
Var old = snapshot();            // 1
Var next = add(2);               // 12; total becomes 12

Lambdas use %!(parameters) => expression or => { statements }. Captured locals are read-only snapshots unless using &name shares them. Only a noncapturing lambda can become a C function pointer. See lambdas.

Ownership, cleanup, and errors

C allocations still need their ordinary owner. Runtime mutable allocations and wide Var boxes belong to a Scope, and canonical String and List values normally last for the process. See scopes and lifetime.

int main(void) {
$scope() {
  File file = $auto(tmpfile());
  file.puts("hello\n");
  Array scratch = [1, 2, 3];
}
return 0;
}

$scope() retains an allocation region around a statement, and $auto(initializer) releases a local at block exit. $lock and $let give scoped locking and temporary replacement. See classes and system macros. defer statement; runs the statement at block exit, in reverse order of registration.

int n = -1;
try {
  if (n < 0) raise %(bad-arg (value $n));
}
catch %(bad-arg *details): {
  printf("bad argument: %s\n", details.repr());
}
finally {
  puts("finished protected work");
}

An Error has a Symbol cause and immutable details. Here $n inserts the variable; a bare n would be an Atom. See errors and cleanup. A Context bounds allocation and error state for a task, and a Thread runs one on a native thread. See contexts and threads.

Compile-time code and source substitution

macro Expression $twice(Expr $value) => $value + $value;
int main(void) {
int result = $twice(3);
return result == 6 ? 0 : 1;
}

A macro takes parsed source. Its hole kinds, such as Expr and Stmt, state what each argument accepts. An argument used twice is evaluated twice: $twice(f()) calls f() twice.

SpellingPurpose
$name(arguments) in codeInvoke a macro or visible meta function
$!(expression), $!{ statements }, $!Unit{ ... }Quote source as syntax for compile-time construction
$(...)Evaluate compile-time Lisp
meta functionExpose a function to compile-time calls; meta native declares a callable supplied by the compiler or a loaded native module
keywordGive a macro a source keyword alias; shipped class and foreach use this mechanism
Decorator macroTransform the following expression, statement, field, function, named type, or unit item

A meta function can also be called at runtime; $f(...) runs it at compile time. with expression as name { ... } substitutes the expression at each use of name. See macros, meta functions, and generating code.

Before bringing over a C source file

x2c parses source before host preprocessing, so most conditional branches must parse, including ones the native build disables. A C macro that supplies missing grammar may need an x2c macro or a rewritten call site. See host preprocessing.

  • Use (void) for an ordinary no-argument function declaration. Do not assume every C dialect, wide/UTF literal spelling, or identifier spelling is accepted; identifier scanning is ASCII. The lexical specification records the exact boundaries.
  • _Static_assert, designated initializers, _Generic, GNU attributes, and statement expressions are supported native forms. _Generic has no x2c result type until a cast supplies one, so it cannot directly drive boxing or method lookup.
  • in and match remain ordinary identifiers outside their contextual forms. Other new keywords can collide with C names. Check the keyword inventory.
  • Literal printf-family formats can trigger conversions of Var arguments. A computed format with a Var argument is rejected. For native arguments, respect the native variadic ABI.

The x2c syntax map lists every addition. The syntax specifications give the exact productions, and the language reference states the exact rules. Idioms shows how to combine them.