Attributes
Built-in attributes
| Attribute | Target | Effect |
|---|---|---|
@profile(t) | file | Set strictness profile (t0, t1, t2) |
@link("lib") | module | Declare native library dependency |
@export / @export("sym") | fn | Export with C calling convention |
@inline | fn | Inline function at call sites (VM and native; native builds also inline small functions on their own). It changes no meaning: arguments convert as for a call |
@constant_time | fn | Checked to have no secret-dependent branch, loop count or address (below) |
@any_thread | fn | C may call it on a thread of its own; checked like a thread’s function (Linking & FFI) |
@public | parameter of a @constant_time fn, struct field | The value is not secret |
@keeps / @keeps(c) | fn parameter | The function keeps a view of the argument past the call: a temporary there is an error (below) |
@centroid | field of a #shader stage’s vertex output | Interpolated at a point the triangle covers, so multisampling never extrapolates it; its derivatives become less accurate (#shader) |
@packed | struct | No padding between fields, alignment 1 |
@align(N) | struct | Alignment at least N (a power of two, at most 16) |
@uninitialized | local var | Its storage is not zeroed: write before you read (Variables) |
@opaque | struct | Fields inaccessible outside the defining module |
@swizzle pattern: Type | struct | Enable vector swizzle access |
Built-in comptime intrinsics
These @name(...) forms run in @tape code; @sizeof and @alignof also
work in runtime code:
| Intrinsic | Returns | Description |
|---|---|---|
@sizeof(T) | i64 | Size of type in bytes |
@alignof(T) | i64 | Alignment of type |
@typeof(T) | TypeInfo | Type metadata (Reflection) |
@has_field(T, "name") | bool | Whether struct has a field |
@has_method(T, "name") | bool | Whether type has a method |
@inner_type(T) | type | Inner type of pointer/slice/optional/array |
@field_type(T, "name") | type | Type of a struct field |
@assert(cond) | void | Compile-time assertion (in a #test region, a test assertion; elsewhere in code that runs, use @trap or an if) |
@embed("path") | []const u8 | Embed file at compile time |
@print(...) | void | Print at compile time |
@println(...) | void | Print with newline at compile time |
@compile_error(msg) | never | Stop compilation with msg (diverges, like @trap) |
Runtime builtins of note: @select(c, a, b) and @declassify(e)
(constant-time functions), @cpu_has(f) and
the AES and carry-less multiply instructions
(Vectors), @trap("msg"),
@move_bytes(dst, src, n) / @fill_bytes(dst, byte, n), memory’s
block move and fill (Pointers),
@offset(p, n), the address n elements past a pointer
(Pointers), the codec
vector operations @avg, @add_sat, @sub_sat, @madd, the
interleaves and the even and odd lanes (Vectors),
@sqrt(x), the machine’s square root, @mul_hi(a, b), the high 64
bits of the 128-bit product of two u64s or two i64s (a * b is the low
half), @add_carry(a, b, cin, &cout) and @sub_borrow(a, b, bin, &bout),
the low 64 bits of a + b + cin (a - b - bin) with the carry (borrow)
out written to a u64 as 0 or 1, for multi-limb arithmetic (u64
operands; any non-zero carry in counts as 1; no branch, so they are
allowed in @constant_time code; a chain of them through one carry
variable, s1 = @add_carry(x1, y1, c, &c), compiles to a chain of
add-with-carry instructions with the carry kept in the processor’s flags,
as fast as hand-written carries or faster), and @atomic_load(p), @atomic_store(p, v), @atomic_add(p, v)
and @atomic_cas(p, old, new), one indivisible, sequentially consistent
operation on an i32, u32, i64 or u64 word that threads share
(Pointers).
User-defined attributes
Declare custom attributes with the attr keyword:
attr serializable;
attr deprecated(message: []const u8);
@serializable
struct Config {
name: []const u8;
value: i64;
}
@deprecated("use new_api() instead")
pub fn old_api() -> i64 {
return new_api();
}User attributes can be attached to functions, structs, struct fields, enum variants and tagged union variants. They accept up to 16 arguments. Attribute names cannot shadow built-in names (profile, runtime, link, export, inline, constant_time, public, keeps, centroid, asm, tape, emit, packed, opaque, align, sizeof, alignof, assert, typeof, print, println, compile_error).
Constant-time functions
Code that handles keys and other secrets must not let a secret decide when
something happens: a branch, a loop count or a memory address that depends
on a secret shows in the running time and in the cache. @constant_time
makes the compiler check a function for that, and keep it true in the
machine code it emits.
@constant_time
pub fn ct_eq(a: []const u8, b: []const u8) -> bool {
if (a.len != b.len) { return false; } // lengths are public
var diff: u8 = 0;
for (i in 0..a.len) { // the loop count: public
diff = diff | (a[i] ^ b[i]); // secret bytes, public index
}
return diff == 0; // a comparison as a value
}Parameters are secret unless marked @public, and so is everything computed
from a secret. A struct field marked @public holds no secret. In a
@constant_time function these are errors when they involve a secret:
if,while, aforrange,match,is,or return,or { },x!,== null(a branch or a loop count);&&and||(they branch: combine secret bools with&,|,^or@select);- an index, a slice bound or an integer cast to a pointer (an address);
/and%, unless dividing by a power-of-two literal;- float operations;
- passing a secret to a function that is not
@constant_time(an extern fn, a function pointer, string interpolation included) or to a@publicparameter, storing one in a@publicfield; - using a module-level
varat all.
The error names the rule and how the value became secret:
pad.tape:4:9: error: branch on a secret in @constant_time fn 'pad': the condition reads 'n' (...)
'n' is secret: line 3 assigns it '(block[63] & 63) as i64'
'block' is secret: parameter 'block' (line 2) is not @publicTwo builtins go with it:
| Builtin | Result |
|---|---|
@select(c, a, b) | a if c else b; both evaluated, never a branch (integers, bools, enums, vectors; any function) |
@declassify(e) | e, now public: what may be revealed, such as whether a MAC verified (only in a @constant_time function) |
@constant_time
fn cswap(swap: bool, a: *u64, b: *u64) {
let x: u64 = *a;
let y: u64 = *b;
*a = @select(swap, y, x);
*b = @select(swap, x, y);
}The machine code of a @constant_time function has no conditional branch
its source does not have, on x86-64 and arm64, optimized or not; zeroing
key material before returning is never optimized away. The timing promise
is native code’s: tape run gives the same results, not the same timing.
The bits rotations, byte swaps, loads and stores and bytes.copy, move,
fill and zero are @constant_time, so constant-time code can call them.
Parameters that keep a view
A function that keeps a view of its argument past the call (a container
storing the element, a setter storing a []const u8) marks the parameter
@keeps:
import str from "str";
var label: []const u8 = "";
fn set_label(@keeps s: []const u8) {
label = s; // kept after set_label returns
}
fn main() -> i32 {
set_label(str.from_view("hi")); // error: the string is freed after this statement
let name = str.from_view("hi");
set_label(name); // fine: name lives on
set_label("literal"); // fine: a literal is static
return 0;
}The call with a temporary is refused: “a temporary string would be freed
while ‘set_label’ keeps a view of it (parameter ‘s’ is @keeps); bind it to a
name first”. The temporaries are the ones a view may not be stored from
(Ownership): an owned string
a call returns, a concatenation, an interpolation, and a struct or array
literal holding one. A named string, a literal and a value the callee takes
by value (Vec_string.push(make()) copies the string) are fine.
@keepsgoes only on a parameter whose type holds a view or a pointer (a slice, a pointer, or a struct, array or?Tholding one); on ani64or astringit is an error.@keeps(c)takes a compile-time bool, for generators:@emit pub fn Vec_$T.push(*self, @keeps(${fixed.holds_view(T)}) item: $T)keeps forVec_cslice_u8, not forVec_i64.- It is part of the signature: other modules’ calls are checked too, and
@moduleinfogives each parameter’skeeps. A call through a function pointer is not checked. - The stdlib’s containers mark their element-taking methods (
push,set,insert, a map’ssetkey and value, …) when the element holds a view.
Qualified attributes
An attribute is private to its module unless declared pub attr.
Attributes from other modules use dot syntax:
import mylib from "mylib"; // mylib declares: pub attr serializable;
@mylib.serializable
struct Data { x: i64; }Compile-time attribute access
In @tape code, a field descriptor from @typeof(T).fields answers
.has_attr(a) and gives an attribute’s arguments with .attr(a), read by
the parameter names of its attr declaration:
attr serializable;
attr max_length(n: i64);
@tape fn count_serialized(T: type) -> i64 {
var c: i64 = 0;
for (field in @typeof(T).fields) {
if (field.has_attr(serializable)) { c = c + 1; }
}
return c;
}field.attr(max_length).n reads an argument; .attr() of a marker
attribute (no parameters) is a compile error. See
Reflection.
Last modified: