Compile-time intrinsics
Every compiler-known builtin uses the #name(...) sigil: one uniform spelling, distinct from a regular function call. The sigil is mandatory — a bare call to a builtin is a fix-it error — so a compiler-known operation is self-evident at the call site, never confused with a library function. They fall into a few families.
| Family | Intrinsics |
|---|---|
| Typed queries | #size_of::[T](), #align_of::[T](), #addr_of(place), #addr(p), #zero::[T]() |
| Data embedding | #include_bytes("path"), #include_str("path"), #env("NAME") |
| Output | #println(...) (the single-file print builtin; the library io::println is a normal function and needs no sigil) |
| FFI / raw | #str_ptr(s), #slice_ptr(s), #slice_len(s), #str_from_raw_parts(ptr, len) |
| Byte order | #bswap16/32/64(x), #htons(x), #htonl(x), #ntohs(x), #ntohl(x) |
| CPU hints | #cpu_relax() |
| ObjC + GPU FFI | #selector("name"), #msg_send(recv, "sel", ...) -> RetTy, #compile_shader("file.metal", "msl") |
#addr_of(place): address of a place as *T
Returns *T for the type of the addressed place. The returned pointer aliases existing storage and the borrow checker does not track its lifetime, so taking the address is the marker — there is no wrapper. Use it when a C function writes through a pointer.
extern fn time(t: *i64) -> i64;
fn now() -> i64 {
var t: i64 = 0;
time(#addr_of(t));
return t;
}
The argument must be a place expression (an identifier, field access, index, or dereference chain). A call result or arithmetic temporary is rejected, and there is no turbofish form (E0501).
#addr(p): a pointer's address as usize
The loud pointer-to-integer form: it takes a pointer and returns its address as a usize. Use it whenever you need to inspect or compute on a raw address; the intrinsic spelling keeps the conversion visible at the call site.
let n: usize = #addr(p);
let i: i32 = n as i32;
#zero::[T](): an all-zero value
Safe, and useful for C-style aggregate initialization where you fill selected fields afterward. It is also accepted in const / static initializers.
var p: Point = #zero::[Point]();
p.x = 10;
#size_of::[T]() and #align_of::[T]()
Return usize. Safe, with no memory access; LLVM folds the call to a constant at -O1 and above. Substitution propagates through monomorphization, so the value is correct for every instantiation of a generic.
let bytes: usize = #size_of::[T]() *% (n as usize);
let p: *u8 = malloc(bytes);
#include_bytes / #include_str: embed a file at compile time
#include_bytes("path") embeds a file as a *[u8; N] where N is the file length, known at compile time. #include_str("path") does the same but returns a str and requires valid UTF-8. Paths resolve relative to the source file containing the call.
let shader: *[u8; 2048] = #include_bytes("../shaders/double.metallib");
let manifest: str = #include_str("config.txt");
The bytes live in .rodata. Errors: E0870 (path not found), E0871 (non-literal argument), E0872 (over the 64 MiB limit), and E0875 (invalid UTF-8, for #include_str).
#env("NAME"): read an environment variable at compile time
Returns a str pointing at a .rodata global with the value the compiler saw. Useful for baking build-time config into a binary.
let greeting: str = #env("GREETING"); // resolved at sema time
Errors: E0903 (non-literal argument), E0876 (variable not set when cpc ran). There is no optional form; use a sentinel and check the length at runtime if you need "missing".
#cpu_relax()
A spin-loop CPU hint. It lowers to the platform pause/yield instruction where available, and to nothing elsewhere. Safe, returns ().
ObjC and GPU intrinsics
#selector, #msg_send, and #compile_shader are the load-bearing primitives the appkit and metal bindings sit on. Direct use is rare; consume them through those packages. See FFI for the Objective-C mechanics.