Unchecked Intrinsics
This section describes intrinsics that require a checked block.
Syscall Intrinsic
The @syscall intrinsic performs a direct system call to the operating system.
syscall_intrinsic = "@syscall" "(" syscall_number { "," argument } ")" ;
syscall_number = expression ;
argument = expression ;
The @syscall intrinsic takes at least one argument (the syscall number) and at most seven arguments (syscall number plus six syscall arguments). All arguments must be of type u64.
The @syscall intrinsic returns an i64 value representing the result of the syscall. On Linux x86-64, negative values typically indicate errors. The exact behavior depends on the syscall being invoked and the platform.
Syscall numbers and conventions differ between operating systems. Linux x86-64 syscall numbers are different from macOS aarch64 syscall numbers. Users should consult platform-specific documentation.
fn main() -> i32 {
let msg: [u8; 3] = [72, 73, 10]; // "HI\n"
let write_num: u64 = 1; // Linux x86-64: write
let fd: u64 = 1; // stdout
let exit_num: u64 = 231; // Linux x86-64: exit_group
let code: u64 = 0;
checked {
// write(fd=1, buf, len)
let msg_ptr: u64 = @ptr_to_int(@raw(msg[0]));
let msg_len: u64 = 3;
let result = @syscall(write_num, fd, msg_ptr, msg_len);
// exit_group(code)
@syscall(exit_num, code);
};
0
}
Pointer Arithmetic Intrinsic
The @ptr_offset(p, n) intrinsic performs standard pointer arithmetic: it returns the pointer whose address is address_of(p) + n * size_of(T), where T is the pointee type of p. A positive n moves toward higher addresses and a negative n toward lower addresses. This is a plain addition of the scaled offset and is uniform for every pointer origin — a pointer into a local array, a heap allocation, a memory-mapped region, or an address produced by @int_to_ptr all advance identically. Because array elements are laid out ascending (§3.5), advancing a pointer to element i by 1 yields a pointer to element i+1, so @ptr_offset and array indexing agree. Offsetting outside the bounds of the pointed-to allocation is undefined behavior (see ADR-0028).
fn main() -> i32 {
let arr: [i32; 3] = [10, 20, 30];
checked {
let base: ptr const i32 = @raw(arr[0]);
// base points at element 0; +1 advances to element 1 (value 20).
@ptr_read(@ptr_offset(base, 1))
}
}
Heap Allocation Intrinsics
The @alloc, @free, and @realloc intrinsics provide raw, unchecked access to the heap. Like the raw-pointer intrinsics they may only appear inside a checked block (§9.1). They are the primitives on which safe, owned collections (for example a source-level ArrayBuf) are built: the unsafety is confined to the collection's internals behind a checked API.
@alloc(count) allocates a heap block large enough to hold count elements of type T — that is, count * size_of(T) bytes of uninitialized storage — and returns a ptr mut T addressing the block. The element type T (and hence the result type ptr mut T) is inferred from the surrounding context, exactly as for @int_to_ptr. count must have type u64. On allocation failure the returned pointer is null; the caller is responsible for checking. The returned pointer is suitable for @ptr_offset/@ptr_read/@ptr_write over the count elements (element i at @ptr_offset(p, i)).
@free(p, count) releases a block previously returned by @alloc/@realloc, where p is a ptr mut T and count (a u64) is the element count that was allocated. Using p after it is freed is undefined behavior. Freeing a null pointer is permitted and has no effect.
@realloc(p, old_count, new_count) resizes the block addressed by p (a ptr mut T) from old_count to new_count elements and returns a ptr mut T to the resized block, which may differ from p. The first min(old_count, new_count) elements are preserved. If p is null it behaves like @alloc(new_count). old_count and new_count must have type u64. The result type is the same pointer type as p.
@alloc, @free, and @realloc may only be used inside a checked block. The element-count arguments (count, old_count, new_count) must be u64, and the pointer arguments of @free/@realloc must be a mutable pointer ptr mut T. The result type of @alloc must be resolvable to a ptr mut T from context.
fn main() -> i32 {
checked {
// Allocate room for 4 i32s, write three, read one back, then free.
let p: ptr mut i32 = @alloc(4);
@ptr_write(p, 10);
@ptr_write(@ptr_offset(p, 1), 20);
@ptr_write(@ptr_offset(p, 2), 30);
let grown: ptr mut i32 = @realloc(p, 4, 8); // contents preserved
let v: i32 = @ptr_read(@ptr_offset(grown, 1)); // 20
@free(grown, 8);
v
}
}
Field Pointer Intrinsic
The @field_ptr intrinsic forms a raw pointer to a struct field place without taking a reference, the analog of Rust's &raw mut (*p).field. Like the other raw-pointer intrinsics it may only appear inside a checked block (§9.1). It is compiler-mediated field access: the pointer addresses the field at the offset the compiler assigns, so unchecked code can walk a struct without hardcoding slot offsets, remaining correct even if the struct layout is implementation-defined.
field_ptr_intrinsic = "@field_ptr" "(" field_access ")" ;
field_access = expression "." IDENT ;
The @field_ptr intrinsic takes exactly one argument, which MUST be a field-access expression s.field. Applying it to any other expression (a bare variable, an array element, a call result, or a literal) is a compile-time error; use @raw/@raw_mut to address those places.
@field_ptr(s.field) returns a ptr mut F, where F is the type of field, addressing the storage of that field within s. The result addresses the same location as @raw_mut applied to the field, and reading it with @ptr_read observes the same value as the ordinary field access s.field. Because the returned pointer is mutable, @ptr_write through it updates the field in place.
struct Mixed { a: i32, b: i64, c: bool }
fn main() -> i32 {
let m = Mixed { a: 11, b: 22, c: true };
let got: i64 = checked {
let p: ptr mut i64 = @field_ptr(m.b);
@ptr_read(p) // 22, same as m.b
};
@intCast(got)
}