12. Decorators
Decorators are compiler instructions prefixed with @. They act at compile time, never at run time. Placing @target, @singleton, @returns, @ensures, a @c_* decorator, @displayable or @assert where it does not apply is an error.
| Decorator | Target | Purpose |
|---|---|---|
@comptime |
fn / value | Evaluates at compile time (§14) |
@extern, @extern("symbol"), @extern(LIBRARY, "symbol") |
fn without body | Declares a foreign function (§13) |
@c_pointer |
parameter of an @extern fn |
Passes the parameter's address (§13) |
@c_out |
parameter of an @extern fn |
C writes through the parameter (§13) |
@c_type("size_t") |
parameter or result of an @extern fn |
The C spelling of an integer type (§13) |
@c_header("<header.h>") |
@extern fn |
Takes the prototype from a C header (§13) |
@c_struct, @c_struct("struct name") |
struct | Mirrors a C struct (§13), named by an optional C type name |
@c_variadic(N) |
@extern fn |
The C function is variadic after N fixed parameters (§13) |
@intrinsic |
fn or impl without body | The host runtime provides the body (@intrinsic, below) |
@export, @export("name") |
fn | Exposes the function under an unmangled name (§13) |
@test |
fn | Marks a test, run by vader test (docs/TOOLING.md Tests) |
@deprecated("reason") |
any declaration | Warns at every use; the code still compiles |
@assert(cond), @assert(cond, "message") |
top level | Fails the build unless cond is true at compile time |
@partial |
match |
Lifts the exhaustiveness check (below) |
@unreachable |
match |
Lifts the exhaustiveness check and traps on the missing cases (below) |
@no_return |
fn | The function never returns (below) |
@internal |
struct field | The field is visible only in its module (below) |
@target, @target(.Os, ...) |
top-level fn | Selects the body for the target OS (§17 Target selection) |
@displayable |
enum | Generates Display: each variant renders as its name (§4 Enums) |
@singleton |
top-level fn without parameters | Runs the body once and returns the same instance to every call (below) |
@returns(cond) |
top-level fn returning bool |
The function returns the value of cond (§3 Declared guards) |
@ensures(cond) |
top-level fn | cond holds when the function returns (§3 Declared postconditions) |
@allow_unused |
any top-level declaration | Exempts the declaration from the dead-code warning (below) |
@intrinsic#
@intrinsic and @extern both mark a function whose body is not written in Vader. @extern is for user code that calls a foreign library. @intrinsic is for the standard library, whose body the host runtime provides (collect, the members of string implements Hash).
A free function without a body must carry one of the two. A named function type is therefore written inline where it is used, never as a bodiless declaration. A build fails if an intrinsic has no host implementation.
@intrinsic applies to a function or to a whole impl:
// Standalone fn: a free-function host import (I/O, GC, builders...).
@intrinsic
export collect :: fn() -> void
// Trait impl: every method of the impl is provided by the host.
@intrinsic
string implements Hash
@assert#
The condition must evaluate to true at compile time; otherwise the build fails. The optional message must be a string literal without interpolation, and is added to the error.
@partial and @unreachable#
Both lift the exhaustiveness check on a match.
@partialis for "everything else does X". Pair it with a_arm; the compiler then does not warn about that wildcard.@unreachableis for "the other cases cannot happen". The arms handle the possible cases explicitly, with no_arm, and a value that reaches no arm stops the program (reached unreachable).
The two cannot be combined.
@no_return#
The function never returns to its caller: it stops the program or loops forever. A call to it has type never, so it fits any slot (_ -> todo("x") where a value is expected), ends control flow, and satisfies a non-void function's obligation to return. panic, todo and unreachable in std/abort use it.
@internal#
Struct fields are public by default. @internal restricts one field to the module that declares the struct: another module cannot read it, write it, set it in a literal or bind it in a pattern.
Give an @internal field a default value, so that other modules can still build the struct with T {}. Without a default, only the declaring module can build it.
@singleton#
A module holds constants and functions, no variables. Its state lives in a @singleton function, which runs its body once per program and returns the same instance to every call:
@singleton
log :: fn() -> string[] = []
log().push("x") // every later `log()` sees "x"
- The instance is ordinary heap state, not a frozen value: writes to it persist. One built from a module constant is a copy of it.
- A module constant cannot call a singleton: the constant would change with the instance. A lambda the constant holds may call one, since it runs only when called.
- A singleton takes no parameters, has no type parameters, and cannot use
yield. - An
asyncsingleton hands every call one task, theshare(§16) of its body: concurrent and later awaits all get the same result, and the body runs once. A cancelled await leaves the task to the others, and a body that awaits its own task stops the program. The program must importstd/async. - A call that reaches the singleton again while its body is still running stops the program.
- A singleton cannot run at compile time: only the running program builds it.
- Only a free function can be a singleton.
@allow_unused#
@allow_unused exempts a private declaration from the dead-code warning. Use it for deliberate dead code: an API not wired yet, a helper kept for symmetry, a decoy in a test fixture. The declaration counts as reachable, and so does everything it uses.