Download

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.

  • @partial is for "everything else does X". Pair it with a _ arm; the compiler then does not warn about that wildcard.
  • @unreachable is 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 async singleton hands every call one task, the share (§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 import std/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.