hd-lang

#Annotations

Status: language specification draft.

#Terminology

  • Member metadata is the ordered list of values attached to a struct field, an enum variant, or a parameter.
  • Coherence works one (trait, concrete target) pair at a time over the resolved package graph.

Annotations attach typed values to declarations. They do not alter a declaration's name, type, behavior, or visibility, and they do not discover or register runtime objects automatically.

The design principles are:

  1. attached values are ordinary typed values;
  2. derived behavior is requested explicitly, with @derive, @error, or a derivation block;
  3. runtime information is produced explicitly when requested;
  4. the semantic foundation is ordinary traits, implementations, and values.

An ordinary decorator attaches a value to the item or member it precedes. That is a type-level fact before a struct or enum, or member metadata before a field, variant, or parameter. A derivation block with no trait attaches the same values away from the declaration.

The compiler lowers attached values to fact construction. This is not runtime wrapper execution. @derive(Trait, ...) generates ordinary trait implementations, either through a compiler intrinsic or through a trait's template, as Typed Derivation defines. An ordinary decorator must not silently change a declaration's name, type, behavior, or visibility.

Note. Information derived from a type is an ordinary trait with an associated function, derived through a template, as in trait Validate: fn validator() -> Validator read with User::validator(). The earlier facet protocol (Annotation, Annotate, the annotator traits, AnnotationRef, and annotate Facet for Target) was removed by Typed Derivation decision 10 (2026-09-27). Building such a value once per type, with deferred Ref[T] references for recursive types, is the standard library's derived-function cache.

#Two annotate Forms

Values are attached in two forms. One is a prefix decorator on the declaration or member. The other is a member line of a derivation block with no trait, for shared metadata written away from the declaration.

Note. This heading keeps its earlier name so that links to it stay valid. The annotate Target: block and the reserved word annotate were removed by Typed Derivation M26 (2026-09-28).

#Prefix Decorators

An @value line immediately before a module-level struct or enum declaration attaches a type-level fact, as Facts defines. It does not register or derive anything.

A decorator may precede any item or member, and attaches its value to it. Items are functions, structs, enums, traits, implementations, and newtypes. Members are fields, variants, value parameters, and methods:

struct Tool:
    name: string

fn tool(name: string) -> Tool:
    Tool { name: name }

@tool("search")
fn search(query: string) -> string:
    query

@"storage"
trait Store:
    @"read"
    fn load(self, @"key" key: string) -> string

@"inherent"
impl Tool:
    @"label"
    fn label(self) -> string:
        self.name

@"id"
type ToolId(i64)
  1. annot.decorator.targetsA decorator may precede an item: a module-level function, struct, enum, trait, implementation, or newtype declaration.
  2. annot.decorator.membersA decorator may also precede a member: a struct field, an enum variant, a value parameter, or a method of a trait or implementation.
  3. annot.decorator.attachA decorator before a function, trait, implementation, newtype, or method attaches its value to that declaration and changes nothing else about it.
  4. annot.decorator.no-localsA local declaration, a statement, and an expression take no decorator. A decorator there is an error. Error: decorator-not-top-level.
  5. annot.decorator.no-moduleA module takes no decorator, because it has no declaration.
  6. annot.decorator.function-deriveA @derive(...) line before a module-level function declaration is an error, reported on the decorator. Error: decorator-not-annotator.
  7. annot.decorator.derive-targetsA @derive(...) line before a trait, an implementation, or a method is the same error. Error: decorator-not-annotator.
  8. annot.decorator.duplicateTwo values of one concrete type before one function, trait, implementation, newtype, or method are an error, reported on the later value. Error: duplicate-fact.
  9. annot.decorator.facts-ofCode reads a value attached to a module-level function f through facts_of(f).find::[M](), as Function Facts defines.
  10. annot.decorator.no-listingNo operation lists the declarations that carry a value, so code cannot enumerate every function with a given decorator.
Error example
@derive(Eq)  # error: decorator-not-annotator
fn same(value: i32) -> i32:
    value

@derive(Eq)  # error: decorator-not-annotator
trait Named:
    fn name(self) -> string

@"first"
@"second"  # error: duplicate-fact
fn twice() -> void:
    pass

A decorator that names a function whose parameters all have defaults calls it, so a marker is written without parentheses:

use std.annotation.facts_of

struct Hidden: pass

fn hidden() -> Hidden:
    Hidden {}

@hidden
fn internal_check() -> bool:
    true

marked := facts_of(internal_check).find::[Hidden]()
  1. annot.decorator.bare-call-defaultsIn a decorator, a bare name that resolves to a function with no parameters without a default is called, so @hidden means @hidden().
  2. annot.decorator.bare-call.onlyThe rule applies only to decorators. Elsewhere, the same name stays a function value.

Why. A decorator is a plain value, as in Java, C#, Kotlin, and Dart, so one rule covers every place it may go. Whatever reads a value checks that it suits its target, so the compiler knows no signatures. Reading one function's facts, never a listing, keeps discovery with tools.

An @value line immediately before a named or embedded struct field or an enum variant attaches its value to that member's metadata. For a field name: string, @max_len(80) attaches max_len(80) to name. Member metadata is also the member's or variant's declaration facts for typed derivation.

For an embedded Timestamps field in Post, @flatten() attaches flatten() to the member Timestamps. A generic embedded Box[T] keeps the member name Box. Metadata is attached only to the embedded field itself; it is not propagated to promoted fields or methods.

Multiple lines retain source order and obey the duplicate rule of Member Metadata. A derivation block with no trait may then extend or replace a member's decorator values. Decorators attach to declarations or members, never to type expressions. Declaration decorators are module-level syntax; local declarations cannot be decorated.

An @value prefix on a parameter of a module-level named function attaches parameter metadata. The prefix may be inline or occupy its own line in a multiline parameter clause:

fn get_user(
    @description("User identifier")
    id: UserId,
) -> User:
    ...

This attaches description("User identifier") to the parameter id. Parameter decorators are also accepted on the value parameters of methods, including trait requirements. They are not accepted on closures, receiver parameters, or local functions.

@derive uses the same prefix position but is not an ordinary decorator. It stays a compiler intrinsic, as @error does (Error Derivation). Its arguments are trait names rather than metadata values. The compiler checks and generates each requested implementation. Opting In defines which traits it accepts.

#Inline And Cold

@inline and @cold choose how the compiler compiles calls of a function. They never change what a program does:

use std.annotation.{cold, inline}

# Runs only on bad input: never copy it, and keep the engine from copying it.
@cold
fn unexpected(at: usize, size: usize) -> string:
    if at >= size:
        return "unexpected end at byte $at"
    "unexpected byte at $at"

# A long loop: keep it a function of its own.
@inline(.Never)
fn checksum(input: &List[u8]) -> u64:
    let hash: u64 = 14695981039346656037
    for byte in input:
        hash = (hash ^ u64(byte)).wrapping_mul(1099511628211)
    hash

# A small reader: prefer a copy of its body at each call.
@inline
fn first(input: &List[u8]) -> u8:
    input.get(0).unwrap_or(0)
  1. annot.inline.declaredstd.annotation declares the enum InlineHint, with the variants Auto, Always, and Never, the function inline(hint: InlineHint = .Auto), the struct Cold, and the function cold(). They are not prelude names, so code imports them.
  2. annot.inline.recognizedThe compiler recognizes std.annotation.inline and std.annotation.cold by their qualified names, through an alias or a module path. Their lines are compiler decorators and attach no value.
  3. annot.inline.targetsAn @inline or @cold line must precede a module-level function or a method that has a body. Before a trait requirement without a default body, or before an @intrinsic declaration, it is an error. Error: invalid-inline-decorator.
  4. annot.inline.argumentThe argument of inline must be .Auto, .Always, or .Never, or left out, and it is read as written. Any other argument of inline, and any argument of cold, is an error. Error: invalid-inline-decorator.
  5. annot.inline.bareA bare @inline is @inline(.Auto), by annot.decorator.bare-call-defaults.
  6. annot.inline.exclusiveAn @inline line and an @cold line before one declaration are an error, reported on the later line. Error: invalid-inline-decorator.
  7. annot.inline.duplicateTwo @inline lines, or two @cold lines, before one declaration are an error, reported on the later line. Error: duplicate-fact.
  8. annot.inline.neverNo build copies the body of a function marked @inline(.Never) or @cold into a caller.
  9. annot.inline.coldIn every build, each direct call of a @cold function is a call through the function table.
  10. annot.inline.instancesA choice holds for every instance of a generic function, and for a method wherever a call resolves to its body. A call through &dyn or a function value is a call as before.
  11. annot.inline.meaningNone of these lines changes a program's output, its panics, or its panic frames.
Error example
use std.annotation.{cold, inline}

trait Step:
    @cold  # error: invalid-inline-decorator
    fn step(self, x: i32) -> i32

@inline(.Sometimes)  # error: invalid-inline-decorator
fn twice(x: i32) -> i32:
    x * 2

@cold
@inline(.Never)  # error: invalid-inline-decorator
fn fail(code: i32) -> string:
    "failed with $code"

@inline
@inline(.Never)  # error: duplicate-fact
fn thrice(x: i32) -> i32:
    x * 3

An @inline or @cold line before any other target, such as a struct, is a decorator-target-kind error, because InlineHint and Cold carry @annotate(.Fn, .Method), by annot.target.limit.kind-error.

Why. A table call is one V8 does not inline, so @cold keeps a rare path out of a hot caller's code in both compilers. The decorators are compiler decorators, not values, because an ordinary value changes nothing by annot.decorator.attach.

#Function Facts

facts_of(f) returns the values that decorators attach to the module-level function f, as a Facts value:

use std.annotation.facts_of

struct Route:
    path: string

fn route(path: string) -> Route:
    Route { path: path }

@route("/users")
fn list_users() -> string:
    "[]"

fn users_path() -> string:
    match facts_of(list_users).find::[Route]():
        .Some(found) => found.path
        .None => ""
  1. annot.facts-of.declaredstd.annotation declares the compiler intrinsic facts_of. It is not a prelude name, so code imports it, as in use std.annotation.facts_of. Its signature is pub fn facts_of[F](f: F) -> Facts.
  2. annot.facts-of.targetA use of facts_of must be a direct call whose one argument names a module-level function declaration. The name may go through a module qualifier or a use import. The argument is not evaluated.
  3. annot.facts-of.resultThe call returns a Facts that holds the values attached to that function, in source order. Missing or extra arguments follow the ordinary call rules. Compiler decorators attach no value, so the result leaves out @derive, @error, @from, @source, @intrinsic, @annotate, @num_suffix, @str_prefix, @inline, and @cold lines.
  4. annot.facts-of.target.errorAny other argument is an error. That covers a closure, a local binding, a parameter, a method, a field access, a call, a type, and a non-function binding. So is facts_of used as a value. Error: invalid-facts-of-target.
Error example
use std.annotation.facts_of

fn get_user(id: string) -> string: id

handler := get_user
handler_facts := facts_of(handler)  # error: invalid-facts-of-target
closure_facts := facts_of(fn(id: string) -> string: id)  # error: invalid-facts-of-target

Why. A function has no Structure, so its values need a read of their own. Typing it as Facts reuses the lookup that templates use for types, members, and variants.

See also: Prefix Decorators, Facts.

#Member Metadata

Member metadata is written on the member with @value lines, or away from the declaration in a derivation block with no trait:

use std.structure.Structure

struct User:
    display_name: string

impl User by Structure:
    display_name = [min_len(1), max_len(80)]

A derivation block with no trait writes metadata for a struct's fields or an enum's variants. A function has no derivation block, so parameter metadata is written only with @value on the parameter.

  1. annot.metadata.placesA field's or variant's metadata is written with @value lines on it and with member lines of a derivation block with no trait for its type.
  2. annot.metadata.params-at-onlyParameter metadata, including a payload parameter's, is written only with @value lines on the parameter.
  3. annot.metadata.reference-storageMember metadata and parameter metadata are stored as shared references in List[*dyn Inspectable].
  4. annot.metadata.valueAny value that a global may hold may be attached to any item or member; no marker trait is required. Only a fact type's target kinds limit where it goes.
  5. annot.metadata.eval-as-factEach metadata expression is evaluated as a fact expression is, and under the same block_on ban.
  6. annot.metadata.duplicateTwo metadata values of one concrete type on one member, variant, or parameter are an error, reported on the later value. Error: duplicate-fact.

One member or parameter must not contain two metadata values with the same concrete type.

Metadata objects are ordinary values. Constructors and helper functions are equivalent ways to make them:

struct MaxLen:
    value: i32

fn max_len(value: i32) -> MaxLen: MaxLen { value: value }

The language does not check that a metadata value suits its member's type, unless its type opts in as a typed fact type. Otherwise the code that reads the value checks it.

Note. arbitrary.with opts in, so a generator of the wrong type is an error at its decorator, as std-testing.arbitrary.with.checked states in the stdlib tier.

Reusable compositions are ordinary values or lists, not new language syntax:

use std.inspect.Inspectable

let email_metadata: List[*dyn Inspectable] = [
    &min_len(3),
    &max_len(320),
    &contains("@"),
]

Different concrete metadata types coexist in one List[*dyn Inspectable]. A member line may name such a list directly, as email = email_metadata, since its right side is any list-typed expression.

#Target Kinds

A fact type may limit the kinds of target that its values attach to, with the standard @annotate decorator:

Error example
use std.annotation.annotate

@annotate(.Field)
struct MaxLen:
    value: i32

fn max_len(value: i32) -> MaxLen:
    MaxLen { value: value }

struct Profile:
    @max_len(80)
    name: string

@max_len(3)  # error: decorator-target-kind
fn greet() -> string:
    "hi"

std.annotation declares the kinds and the limiting fact type:

pub enum Target:
    Fn
    Data
    Enum
    Newtype
    Field
    Variant
    Param
    Trait
    Impl
    Method

@annotate(.Data, .Enum)
pub struct Annotate:
    pub kinds: List[Target]

pub fn annotate[T = *dyn Any](kinds...: List[Target]) -> Annotate:
    Annotate { kinds: kinds }

Each target has one kind:

Rule Target Kind
annot.target.kind.fnFunction a module-level function declaration .Fn
annot.target.kind.dataStruct a struct declaration .Data
annot.target.kind.enumEnum an enum declaration .Enum
annot.target.kind.fieldField a named or embedded struct field, or a variant payload member .Field
annot.target.kind.variantVariant an enum variant .Variant
annot.target.kind.paramParameter a value parameter of a function or method .Param
annot.target.kind.traitTrait a trait declaration .Trait
annot.target.kind.implImplementation an implementation, including a derivation block .Impl
annot.target.kind.methodMethod a method or associated function of a trait or implementation .Method
annot.target.kind.newtype-kindNewtype a newtype declaration .Newtype
  1. annot.target.declarationsstd.annotation declares Target, Annotate, and annotate. They are not prelude names, so code imports them, as in use std.annotation.annotate.
  2. annot.target.limitA struct or enum F whose type-level facts include an Annotate value limits values of type F to targets whose kind that value lists.
  3. annot.target.limit.kind-errorA value of a limited type attached to a target of any other kind is an error. It is reported on the decorator or member line that attaches it. Error: decorator-target-kind.
  4. annot.target.unlimitedA type without an Annotate fact is not limited: its values may be attached to any target, as @"note" may.
  5. annot.target.recognizedThe compiler recognizes std.annotation.Annotate by its qualified name. A type of another package named Annotate limits nothing.
  6. annot.target.bootstrapAnnotate itself carries @annotate(.Data, .Enum), so an Annotate value may be attached only to a struct or an enum.
  7. annot.target.kind-only.untypedFor an untyped fact type, one that is not typed, the compiler checks only the kind. Whether its value suits its target's type or signature is checked by the code that reads the value.

Note. Only some targets have a reader in the language. User code reads a function's values through facts_of, and the values on a struct or enum, its fields, and its variants through typed derivation. The values on the other targets, such as traits, implementations, methods, newtypes, and parameters, are for tools.

Why. Java, C#, Kotlin, and Dart check declared target kinds the same way. For an untyped fact, a signature check belongs to the reader, which knows what it needs. A typed fact writes its check as an ordinary type, solved as a call's argument is, not in a separate pattern language.

See also: Prefix Decorators, Literal Suffixes.

#Member-Typed Facts

A fact type may describe the type of its target with a pattern over its own type parameters. The simplest pattern is one parameter alone, which stands for the whole target type. Each value is then checked against the fact type at that target's type:

Error example
use std.annotation.annotate
use std.num.Integer

@annotate::[T](.Field)
struct Fact[T]:
    value: T

fn fact[T](value: T) -> Fact[T]:
    Fact::[T] { value: value }

@annotate::[F](.Field)
struct Positive[F < Integer]: pass

fn positive[F < Integer]() -> Positive[F]:
    Positive::[F] {}

struct Account:
    @fact(5)
    a: i32
    @fact("x")  # error: type-mismatch
    b: i32
    @positive
    id: i64
    @positive  # error: unsatisfied-trait-bound
    name: string

struct Tally[T < Integer]:
    @positive
    hits: T

Note. This heading keeps its earlier name so that links to it stay valid. The MemberTyped marker and @member_typed were removed in batch 39 (2026-10-01).

Each attachment checks like a let binding of the value at the fact type for its target:

Target Decorator Checks like
a: i32 @fact(5) let f: Fact[i32] = fact(5)
b: i32 @fact("x") let f: Fact[i32] = fact("x"), a type-mismatch
name: string @positive let f: Positive[string] = positive(), where Positive[string] breaks F < Integer
hits: T in Tally[T < Integer] @positive let f: Positive[Integer] = positive(), where Integer is one fixed type that implements Integer

The check has three steps:

Step What it does Rules
1 Makes the target's type monomorphic annot.typed-fact.monomorphic
2 Infers the pattern's parameters from that type, as a call infers them from its one argument annot.typed-fact.infer
3 Checks the value at the fact type with the inferred arguments annot.typed-fact.check
  1. annot.typed-fact.declareA struct or enum whose @annotate decorator writes a type argument, as in @annotate::[T](.Field), is a typed fact type. The compiler recognizes std.annotation.annotate by its qualified name and reads that argument.
  2. annot.typed-fact.patternThat type argument is the fact type's pattern, a type over the fact type's own type parameters, such as T, List[T], or fn(T) -> R. It describes the type of each target that the fact type's values attach to.
  3. annot.typed-fact.pattern.scopeNames in the pattern see the fact type's type parameters, as the fact type's own fields do.
  4. annot.typed-fact.pattern.trivialA pattern that is one of those parameters alone, as T in @annotate::[T](.Field), is a trivial pattern. That parameter is the fact type's target parameter, and it is inferred as the target's whole type.
  5. annot.typed-fact.pattern.concreteA pattern that mentions none of the fact type's parameters, as i32 in @annotate::[i32](.Field), is valid. A value of that fact type attaches only to a target of that type, and any other target is an error. Error: type-mismatch.
  6. annot.typed-fact.untyped.reference-valuesA fact type whose annotate type argument is the default *dyn Any, as in @annotate(.Field), is an untyped fact type. Its values stay unchecked, as Member Metadata states.
  7. annot.typed-fact.untyped.no-annotateA fact type without an Annotate fact is untyped too.
  8. annot.typed-fact.targetsA value of a typed fact type may be attached only to a field or a module-level function. A field is a named or embedded struct field, or a payload member. Error: decorator-target-kind.
  9. annot.typed-fact.declared-typeA target's type is the type written on the field, so for hits: *mut Counter it is *mut Counter. A function's type is its signature as a function type, with its ! and requirement row, as in fn(i32) -> string.
  10. annot.typed-fact.monomorphicFor the check, a target's type is made monomorphic. Each type parameter it mentions, of a generic function or of a field's owner, is written as its bound, and an unbounded one as Any.
  11. annot.typed-fact.monomorphic.boundThere, a bound such as Integer or Integer & Display stands for one fixed type that satisfies it, the same at each mention. It is not a dyn type, so any bound is valid there.
  12. annot.typed-fact.inferThe fact type's parameters that the pattern mentions are inferred as if the monomorphic target type were the one argument of a call to infer[P](annotatee: Q). Here P is the fact type's type-parameter list with its bounds, and Q is the pattern.
  13. annot.typed-fact.infer.callThat call follows the ordinary rules for a call that infers its type arguments, by fn.generic.call-list and types.generic.infer. A function target passes as a function value does, by fn.type.named-value and fn.type.declared-variance.
  14. annot.typed-fact.infer.suspendingSo a suspending function does not match a pattern written with fn, such as fn(T) -> R. It could not be passed to a parameter of that type either. Its fn! type is a SuspendFn, and the pattern is an Fn, by fn.type.ctor.sugar.
  15. annot.typed-fact.infer.rowLikewise, a function with a requirement row does not match a pattern whose row is empty, such as fn(T) -> R, by req.row.subsume.missing.
  16. annot.typed-fact.infer.mismatchA target whose type does not fit the pattern is an error, as an i32 field or a two-parameter function is under fn(T) -> R. Error: type-mismatch.
  17. annot.typed-fact.check.boundsAn inferred argument that breaks the bound of its parameter, as string breaks F < Integer in Positive[F < Integer], is an error, by trait.bound.unsatisfied. Error: unsatisfied-trait-bound.
  18. annot.typed-fact.checkA decorator or member-line element v whose type constructor is a typed fact type D then checks like let f: D[A] = v. A holds, in the order of D's type parameters, the inferred arguments and a _ for each parameter the pattern does not mention. Error: type-mismatch.
  19. annot.typed-fact.check.other-paramsEach type parameter of D that the pattern does not mention is inferred from v as a _ slot is, by fn.generic.placeholder.solve, .default, and .unsolved. Error: cannot-infer-type.
  20. annot.typed-fact.check.inferredType arguments in v are inferred from that expected type, by fn.type.generic.argument.sources, as in any such binding.
  21. annot.typed-fact.check.reportedEvery error of the inference and the check is reported on the decorator or member line.
  22. annot.typed-fact.readA template reads such a fact typed, through the field's handle, as annot.handle.fact.pattern states.

Note. In @annotate(.Field), nothing solves T, since neither kinds nor Annotate mentions it. So T takes its default Any, by the ordinary rule types.generic.default.fill.

A fact type may have type parameters that its pattern does not mention. In this example, @range(0, 100, "percent") on level: i32 checks like a binding at Range[i32, G], with G solved from the value as string. Port's pattern i32 mentions no parameter, so a Port value attaches only to an i32 target:

Error example
use std.annotation.annotate

@annotate::[F](.Field)
struct Range[F, G]:
    low: F
    high: F
    unit: G

fn range[F, G](low: F, high: F, unit: G) -> Range[F, G]:
    Range::[F, G] { low: low, high: high, unit: unit }

struct Reading:
    @range(0, 100, "percent")
    level: i32

@annotate::[i32](.Field)
struct Port:
    number: i32

fn port(number: i32) -> Port:
    Port { number: number }

struct Server:
    @port(8080)
    listen: i32
    @port(9090)  # error: type-mismatch
    admin: i64

A function target checks against its signature's function type. In this example, @handler on count checks like let f: Handler[fn(i64) -> i64] = handler():

Error example
use std.annotation.annotate

@annotate::[F](.Fn)
struct Handler[F]: pass

fn handler[R]() -> Handler[fn(i64) -> R]:
    Handler::[fn(i64) -> R] {}

@handler
fn count(id: i64) -> i64:
    id

@handler
fn wrap[T](id: i64) -> List[T]:
    []

@handler  # error: type-mismatch
fn later!(id: i64) -> i64:
    id

The generic wrap has the monomorphic type fn(i64) -> List[*dyn Any], where Any is one fixed type, so its line is let f: Handler[fn(i64) -> List[*dyn Any]] = handler(). Likewise fn ms[M < Integer, R](n: M) -> R has the type fn(Integer) -> Any. later is a fn!, so Handler[fn!(i64) -> i64] does not match handler's result.

Note. Writing each parameter as a fixed type that meets its bounds is the same as checking the line inside the declaration's own generic scope. There, too, each parameter is an abstract type with its bounds.

A pattern may take the target's type apart. In this example, @fn_fact on ms infers T = i32 and R = Duration, as a call infer(ms) would for fn infer[T < Num, R](annotatee: fn(T) -> R). The value then checks like let f: FnFact[i32, Duration] = fn_fact():

Error example
use std.annotation.annotate
use std.num.Num

@annotate::[fn(T) -> R](.Fn, .Field)
struct FnFact[T < Num, R]: pass

fn fn_fact[T < Num, R]() -> FnFact[T, R]:
    FnFact::[T, R] {}

struct Duration:
    millis: i32

@fn_fact
fn ms(n: i32) -> Duration:
    Duration { millis: n }

@fn_fact  # error: type-mismatch
fn span(low: i32, high: i32) -> Duration:
    Duration { millis: high - low }

@fn_fact  # error: unsatisfied-trait-bound
fn named(label: string) -> Duration:
    Duration { millis: 0 }

@fn_fact  # error: type-mismatch
fn later!(n: i32) -> Duration:
    Duration { millis: n }

struct Meter:
    @fn_fact  # error: type-mismatch
    count: i32
Target Its type Step 2
ms fn(i32) -> Duration T = i32 and R = Duration, so the value checks at FnFact[i32, Duration]
span fn(i32, i32) -> Duration no match, since fn(T) takes one parameter: type-mismatch
named fn(string) -> Duration T = string, which breaks T < Num: unsatisfied-trait-bound
later fn!(i32) -> Duration no match, since a SuspendFn is not an Fn: type-mismatch
count i32 no match, since i32 is not a function type: type-mismatch

Why. A fact type then accepts exactly what a function with the pattern as its parameter type would accept. The reader needs no rule beyond the ordinary call.

Why. A fact that holds a function of the member's type, such as a test generator, can then be checked where it is written. It need not wait for a test's first run. Facts that hold plain settings need no check, so the check is opt-in. The check is an ordinary call and an ordinary binding, so a fact's pattern and its own signature, such as num_suffix's, state every shape constraint.

Note. The pattern is a decorator's type argument rather than a keyword on a type parameter, so the grammar does not change.

#Grammar

Metadata has no declaration form of its own. Decorator lines are decorator_line in the Annotations grammar. A derivation block with no trait is an impl_decl with by and no for, and its members are derivation_lines, as Traits And Implementations defines:

impl_decl = "impl", [ generic_params ], impl_header_types,
            [ "by", identifier ],
            ( NEWLINE
            | ":", NEWLINE, INDENT,
              impl_member, { impl_member }, DEDENT )
            ;

#Typed Derivation

Typed derivation implements a library's trait for a struct or enum from the type's members. The library writes the implementation once, as a template over the compiler-generated Structure trait. A type opts in with @derive, and configures the result with facts.

@derive(Encode, Keys)
@style(prefix="user_")
struct User:
    id: i64
    @rename("mail")
    email: string
    nickname: string = ""
  1. annot.derive.definitionTyped derivation implements a trait for a struct or enum from the type's members, through the trait's template.
  2. annot.derive.suppliedThe compiler supplies only Structure, the handle constants, and the generated walk, describe, and build bodies.
  3. annot.derive.libraryTemplates, walkers, describers, sources, and facts are ordinary library code.

Why. The compiler generates one statically typed traversal per type. Every format, comparison, or schema is library code over it, so the language needs no macro system and no per-library compiler support.

See also: Derived Implementations, Typed Derivation open points.

#Opting In

@derive(...) lists the traits a declaration derives.

  1. annot.derive.opt-in@derive(...) is the only form that creates a derived implementation from a declaration.
  2. annot.derive.acceptedIt accepts any trait that has a template, the comparison traits of Derived Implementations included.
  3. annot.derive.meansFor a trait X with a template, @derive(X) before a struct or enum T means exactly the derivation block impl X for T by Structure with an empty body. A newtype derives through its base type instead, as Derived Newtypes defines.
  4. annot.derive.no-use@derive(X) needs no use of Structure. The import is needed only where code writes by Structure, as annot.block.structure-use states.
  5. annot.derive.otherAny other trait in a @derive list is an error, reported on the @derive line. Error: underivable-trait.
  6. annot.derive.error-traitError has no template, so @derive(Error) is an error. Error: underivable-trait.
  7. annot.derive.facts-onlyEvery other decorator only attaches information: a configuration decorator such as @style(prefix="user_") attaches a fact and creates no implementation.
  8. annot.derive.overlapListing a trait in @derive and also writing a derivation block for it on the same type is an error, reported on the block. Error: overlapping-impl.
  9. annot.derive.unknownA name in a @derive list that resolves to nothing is an error, reported on the @derive line. Error: unknown-trait.
Error example
use std.error.Error
use std.structure.Structure

@derive(Error)  # error: underivable-trait
enum LoadError:
    Missing

@derive(Sortable)  # error: unknown-trait
struct Coord:
    x: i64

@derive(Show)
struct Point:
    x: i64

impl Show for Point by Structure:  # error: overlapping-impl
    x = []

Note. An error type uses the separate @error intrinsic, as Error Derivation defines.

#The std.structure Module

The module std.structure declares the derivation protocol. The compiler supplies every body written pass:

pub trait Structure:
    fn facts() -> Facts
    fn name() -> string
    fn walk[W < Walker[Self]](self, w: &mut W) -> Result[void, W::Error]
    fn describe[D < Describer[Self]](d: &mut D) -> Result[void, D::Error]
    fn build[S < Source[Self]](s: &mut S) -> Result[Self, S::Error]

pub struct Member:
    pub name: string
    pub position: usize
    pub facts: Facts
    pub doc: string?
    pub embedded: bool
    pub positional: bool
    pub self_ref: SelfRef

pub struct VariantInfo:
    pub name: string
    pub index: usize
    pub facts: Facts
    pub doc: string?
    pub type_doc: string? = .None
    pub of_data: bool
    pub shared: List[(string, *dyn Any)]
    pub self_ref: SelfRef

pub enum SelfRef:
    Absent
    Optional
    Required

pub type Field[S, F] = *FieldRecord[S, F]

pub struct FieldRecord[S, F]:
    pub info: Member

impl[S, F] FieldRecord[S, F]:
    pub fn get(&self, s: S) -> F: pass
    pub fn has_default(&self) -> bool: pass
    pub fn default(&self) -> F?: pass
    pub fn fact[M](&self) -> M?: pass

pub type Variant[S] = *VariantRecord[S]

pub struct VariantRecord[S]:
    pub info: VariantInfo

impl[S] VariantRecord[S]:
    pub fn holds(&self, s: S) -> bool: pass

pub type Members[S] = *MembersRecord[S]

pub struct MembersRecord[S]:
    pub infos: List[Member]

impl[S] MembersRecord[S]:
    pub fn at(&self, position: usize) -> Key[S]?: pass
    pub fn find(&self, matches: fn(Member) -> bool) -> Key[S]?: pass

pub type Key[S] = *KeyRecord[S]

pub struct KeyRecord[S]:
    pub info: Member

pub trait Walker[S]:
    type Error
    fn variant(&mut self, v: Variant[S]) -> Result[void, Self::Error]
    fn member[F](&mut self, h: Field[S, F], value: F) -> Result[void, Self::Error]
    fn rest[T](&mut self, h: Field[S, List[T]], items: List[T]) -> Result[void, Self::Error]: pass

pub trait Describer[S]:
    type Error
    fn variant(&mut self, v: Variant[S]) -> Result[void, Self::Error]
    fn member[F](&mut self, h: Field[S, F]) -> Result[void, Self::Error]

pub trait Source[S]:
    type Error
    fn variant(&mut self, choices: List[Variant[S]]) -> Result[Variant[S], Self::Error]
    fn next(&mut self, members: Members[S]) -> Result[Key[S]?, Self::Error]
    fn member[F](&mut self, h: Field[S, F], previous: F?) -> Result[F, Self::Error]
    fn missing[F](&mut self, h: Field[S, F]) -> Result[F, Self::Error]
  1. annot.structure.declThe standard module std.structure declares Structure, Facts, Member, VariantInfo, Field, FieldRecord, Variant, VariantRecord, Members, MembersRecord, Key, KeyRecord, Walker, Describer, and Source. Facts, Member, VariantInfo and the four record types are struct values: a copy is independent of the value it was copied from. Field, Variant, Members, and Key are read-only counted references to a FieldRecord, VariantRecord, MembersRecord, and KeyRecord respectively.
  2. annot.structure.bodiesThe compiler supplies every body written pass in the declarations above, and the Facts type.
  3. annot.structure.reference-factsFacts holds shared references to the facts attached to one type, member, variant, or module-level function, in source order.
  4. annot.structure.find-copyfacts.find::[F]() copies the first fact of concrete type F through downcast_ref, or returns .None.
  5. annot.structure.find-exactfind compares exact concrete runtime identities, without conversions.
  6. annot.structure.find-keyEach type parameter that F mentions must be bounded by Inspectable, whose evidence supplies its runtime identity to the lookup. Without that bound, the call is an error. Error: unsatisfied-trait-bound.
  7. annot.structure.members.at-optionmembers.at(position: usize) returns .Some(key) for the member at that position.
  8. annot.structure.members.find-optionmembers.find(matches) returns .Some(key) for the first member that matches accepts.
  9. annot.structure.members.lookup-noneEither lookup returns .None when no member fits.
  10. annot.structure.members.key-entryA Key[S] represents a found member and carries its Member information. The keys and the member directory of one variant live as long as the program.
  11. annot.structure.lookup-namesThe names of Members, Key, and the handle methods are fixed by these declarations. Further helpers over them are standard-library design, outside this specification.
  12. annot.structure.self-ref-enumstd.structure also declares the enum SelfRef, whose three values Self References defines.
  13. annot.structure.self-ref-fieldThe compiler computes the self_ref field of every Member and VariantInfo value it supplies.
Error example
use std.inspect.Inspectable
use std.structure.Facts

fn fact_or[M < Inspectable](facts: Facts, fallback: M) -> M:
    match facts.find::[M]():
        .Some(found) => found
        .None => fallback

fn first_or[M](facts: Facts, fallback: M) -> M:
    match facts.find::[M]():  # error: unsatisfied-trait-bound
        .Some(found) => found
        .None => fallback

Note. A concrete F, as in facts_of(list_users).find::[Route](), mentions no type parameter and needs no bound. The key rule holds for every Facts, including one that facts_of returns.

Why. A shared generic body cannot find a fact of type M without knowing M at run time. The Inspectable bound already supplies that identity to downcast_ref, so the lookup reuses it.

Note. Standard walkers, describers, and sources, such as the ones a std.json would use, are library design. The archived STDLIB draft sketches the standard modules.

#The Structure Trait

Structure is the compiler-generated view of one type's members.

  1. annot.structure.sealedStructure is a sealed trait: an impl Structure for T outside the standard library is an error. Error: sealed-trait-implementation.
  2. annot.structure.generatedThe compiler generates Structure for a declared target only while it instantiates a template for that target. No declared type has it otherwise. A tuple type has it as Tuple Structure states.
  3. annot.structure.per-derivationEach derivation sees its own Structure for the target, which reflects that derivation's facts and omitted members.
  4. annot.structure.named-positionsStructure may be named, as a bound or in a call such as T::facts(), only inside a template. Outside one, it may appear only in the use declaration that imports it and after by in a derivation block's header.
  5. annot.structure.named-positions.errorAny other use of Structure, such as the bound in fn fields[X < Structure], is an error. Error: structure-outside-template.
  6. annot.structure.receiverswalk takes the value as self. facts, describe, and build are receiverless.
  7. annot.structure.protocol-refwalk, describe, and build take the walker, describer, or source as a &mut reference, and every method of Walker, Describer, and Source takes &mut self. So a struct walker changes in place, and its caller reads the walker's fields after the traversal.
  8. annot.structure.privatewalk, describe, and build include private members. Opting a type in lets the template's library read every member.
  9. annot.structure.purewalk, describe, and build, and every method of Walker, Describer, and Source, have the empty requirement row and are not suspending.
  10. annot.structure.pure.implAn implementation method of Walker, Describer, or Source that declares a requirement or suspends is an error. Error: trait-method-signature.
  11. annot.structure.nameT::name() returns the target's declared name, with no module path and no type arguments. Like facts, name is receiverless.
  12. annot.structure.name.constantT::name() is a compile-time constant. Like every use of Structure, it may be called only inside a template.
  13. annot.structure.name.newtypeA newtype has its own name: its name() is the newtype's declared name, not its base type's.
  14. annot.structure.name.aliasA transparent alias has no Structure of its own. A derivation through it sees its base type's, so name() is the base type's declared name.
Error example
use std.structure.Structure

struct Point:
    x: i64

impl Structure for Point  # error: sealed-trait-implementation

fn fields[X < Structure]() -> void:  # error: structure-outside-template
    pass

For struct Box[T] declared in the module shapes, T::name() is "Box", not "shapes.Box" or "Box[i32]".

Note. A newtype gets no Structure today (trait.derive.newtype.templated), so no template reads a newtype's name(). A derivation through the base type sees the base type's name. annot.structure.name.newtype applies once newtypes gain a Structure.

Why. Traversal stays pure, as serde's is: input and output happen before build or after walk, so a derivation never needs a provider.

Why. name() gives a template the one piece of type information it needs for a message or a tag, without runtime reflection. Runtime type information stays opt-in through Inspectable. The declared name alone does not change when a file moves.

Note. Once a codec writes name() into its output, renaming the type changes that output, as renaming a type does with serde or Go's encoders.

#Templates

A template is a trait's one derived implementation, written over Structure:

use std.structure.Structure

impl[T] Encode for T by Structure:
    fn encode(self) -> string:
        let mut w = Encoder { style: style_of(T::facts()), out: "" }
        _ := Structure::walk(self, &mut w)
        w.out
  1. annot.template.formimpl[T] Trait for T by Structure: declares the template of Trait.
  2. annot.template.not-implA template is not an implementation. It never applies by itself and claims no (trait, concrete target) pair, so it never overlaps a hand-written implementation.
  3. annot.template.moduleA template must be declared in the module that declares its trait. One declared elsewhere is an error. Error: misplaced-derivation.
  4. annot.template.uniqueA trait has at most one template. A second one is an error. Error: overlapping-impl.
  5. annot.template.structureInside a template, T implements Structure. Its bodies may call Structure::walk(self, w), T::describe(d), T::build(s), and T::facts().
  6. annot.template.qualified-selfInside a template, a call qualified by Structure or by the derived trait, Structure::f(args) or Trait::f(args), has the template's T as its Self. This replaces the Self that trait.assoc-call.trait infers.
  7. annot.template.qualified-self.receiverlessSo a receiverless qualified call is valid in a template. Structure::name() is T's generated name, Structure::facts() is T::facts(), and Encode::name() in Encode's template is Encode's name for T.
  8. annot.template.bodyA template must have a body in which at least one method calls walk, describe, or build.
  9. annot.template.body.errorA template without such a body, such as a bodiless marker template, is an error. Error: marker-template.
  10. annot.template.instanceEach derivation instantiates the template once, as one ordinary implementation of the trait for the target, in the target's module.
  11. annot.template.no-familiesA template never derives an implementation family, a new type, or a builder: it implements the existing trait exactly once per derivation.
  12. annot.template.checkedThe instantiated implementation is type-checked at its opt-in, where the member types and the walker's bounds are both known.
Error example
use std.structure.Structure

trait Tagged:
    fn tag(self) -> string:
        "tagged"

impl[T] Tagged for T by Structure  # error: marker-template

Why. Only the trait's module may declare the template, so a trait has one derivation and a reader finds it beside the trait. A marker template would derive a trait without reading one member.

Note. Among the templates that std declares, the one for Default reads a @default fact to choose an enum's default variant (Derived Default).

Why. A template has exactly one target, so Structure and the derived trait each have one Self inside it. A receiverless call such as Structure::name() or Encode::name() has no argument to infer that Self from.

Note. The generated walk, describe, and build keep their names. This holds even when the derived trait or another trait of T has a method of the same name. The qualified call Structure::walk(self, w) always calls the generated walk.

Note. The receiverless facts and name clash the same way when the derived trait declares its own receiverless member of that name. Inside the template, qualifying the call resolves it. When Encode declares its own name, Structure::name() is the generated one, and Encode::name() is Encode's. Both have T as their Self, by annot.template.qualified-self.

#Tuple Templates

A tuple template derives a trait for every tuple type, at every size, through the tuple's Structure:

use std.function.Tuple
use std.structure.Structure

impl[T < Tuple] Encode for T by Structure:
    fn encode(self) -> string:
        let mut w = Encoder { style: style_of(T::facts()), out: "" }
        _ := Structure::walk(self, &mut w)
        w.out
  1. annot.template.tuple.formimpl[T < Tuple] Trait for T by Structure: declares the tuple template of Trait, where Tuple is std.function.Tuple.
  2. annot.template.tuple.rulesThe template rules for its module, its uniqueness, its body, its Structure calls, and its qualified calls apply to a tuple template as well.
  3. annot.template.tuple.separateA trait may have both a template and a tuple template. @derive uses only the template, so a trait with only a tuple template is not derivable.
  4. annot.template.tuple.implementsA tuple type implements Trait through its tuple template exactly when each of its members meets the obligation of annot.walker.obligation.
  5. annot.template.tuple.unmetA tuple type that fails the obligation does not implement Trait. This is not an error until a use requires the implementation.
  6. annot.template.tuple.overlapA tuple template is an implementation for every tuple type. A hand-written implementation of the same trait for a tuple type is an error. Error: overlapping-impl.
  7. annot.template.tuple.instanceEach tuple type that a program uses with Trait instantiates the tuple template once, as an ordinary implementation.

Why. A tuple has no declaration to write @derive on, so the trait opts every tuple in. A separate form keeps @derive meaning one thing. So a trait that suits only tuples, such as a tuple Display, does not become derivable for structs.

#Derivation Blocks

A derivation block applies one template to one type and may configure it:

use std.structure.Structure

struct Order:
    id: i64
    total_cents: i64
    cache: Cache = Cache {}

impl Encode for Order by Structure:
    Self += [style(prefix="order_")]
    total_cents = [rename("total")]
    cache = pass
  1. annot.block.formimpl Trait for X by Structure: is a derivation block: it applies the template of Trait to X.
  2. annot.block.templateTrait must have a template. A block for a trait without one is an error, and so is a block for Eq, PartialOrd, Ord, or Hash, whose templates only @derive instantiates. Error: underivable-trait.
  3. annot.block.moduleA derivation block must be declared in the module that declares X. One declared elsewhere is an error. Error: misplaced-derivation.
  4. annot.block.structure-useIn an implementation header, Structure after by must name std.structure.Structure, imported with use std.structure.Structure like any other name. Without that import, Structure is unknown. Error: unknown-trait.
  5. annot.block.not-delegationby Structure is never a delegation, as trait.by.structure also states.
  6. annot.block.methodsA block may write a method of the trait. The written method replaces the template's method, as a written method replaces a default.
  7. annot.block.linesA block may carry member lines, which edit the Structure that this derivation sees.
  8. annot.block.localA derivation block's member lines are local to it. Another derivation for the same type sees only the declaration facts.
  9. annot.block.headerA block may state generic parameters and bounds in its header, as in impl[T < Hash] Encode for Bag[T] by Structure:. The derived implementation then has exactly those bounds.
  10. annot.block.newtypeThe target of a derivation block must not be a newtype. A newtype derives only through its base type, with @derive, as Derived Newtypes defines.
  11. annot.block.newtype.errorA derivation block whose target is a newtype is an error, reported on the block. Error: misplaced-derivation.
Error example
use std.structure.Structure

type Meters(i64)

impl Show for Meters by Structure  # error: misplaced-derivation

A module that writes by Structure without importing Structure gets the ordinary unknown-trait error:

Error example
struct Point:
    x: i64

impl Show for Point by Structure  # error: unknown-trait

Note. A foreign type has no derivation exception. Derive on a local mirror type with the foreign type's public fields, and convert, as with serde's remote.

#Member Lines

A member line in a derivation block adjusts facts, or leaves a member out, for that block only. A derivation block with no trait uses the same lines to write shared metadata, as Derivation Blocks With No Trait defines:

Rule Form Meaning for this block
annot.line.extendExtend f += [facts] the declaration facts of f, followed by facts
annot.line.replaceReplace f = [facts] facts in place of the declaration facts of f
annot.line.omitOmit f = pass f is left out of walk, describe, and build
annot.line.selfType-level Self += [facts], Self = [facts] the type-level facts, extended or replaced
  1. annot.line.nameThe name on the left of a member line must be a direct member of the target, or Self. Any other name is an error. Error: unknown-annotation-member.
  2. annot.line.enumIn an enum's block, a member line names a whole variant or Self. A line that names a payload member is an error. Error: unknown-annotation-member.
  3. annot.line.right-typedThe right side must be an expression of a list type, or pass after a member name and =. A named list, as in name = shared_list, needs no spread.
  4. annot.line.right.errorAny other line is an error. That includes f += pass, Self = pass, and pass for a whole variant, such as Busy = pass. Error: invalid-member-line.
  5. annot.line.right.not-listA right side whose type is not a list type, such as name = 5, is this error rather than a type mismatch. Error: invalid-member-line.
  6. annot.line.reference-elementsA member line builds List[*dyn Inspectable], checking typed fact elements by annot.typed-fact.check.
  7. annot.line.duplicateAfter a line applies, one member, variant, or type must not hold two facts of the same concrete type. += with a type already present is an error; = changes it instead. Only a list literal's own elements are checked; a named list that += brings is not. Error: duplicate-fact.
  8. annot.line.unchangedA member without a line keeps its declaration facts.
  9. annot.line.placement-blocksA member line anywhere other than a derivation block, with or without a trait, including in a template or an ordinary implementation, is an error. Error: misplaced-derivation.
Error example
use std.structure.Structure

struct Pair:
    left: i64
    right: i64

impl Show for Pair by Structure:
    left += [rename("l")]
    left += [rename("first")]  # error: duplicate-fact
    right = 5                  # error: invalid-member-line

enum Reply:
    Sent(id: i64)
    Busy

impl Show for Reply by Structure:
    id = [rename("x")]  # error: unknown-annotation-member
    Busy = pass         # error: invalid-member-line

Why. A whole variant cannot be omitted, because build could never produce it. A payload member has no line, because a line names what a derivation walks as a unit.

#Omitted Members

  1. annot.omit.skipA member omitted with = pass is not walked or described, and build never offers it to the source.
  2. annot.omit.defaultbuild fills an omitted member by evaluating its declared default.
  3. annot.omit.no-defaultOmitting a member that declares no default is an error, reported on the member line. Error: omitted-member-without-default.
  4. annot.omit.exemptAn omitted member's type need not satisfy any walker's, describer's, or source's bound.
  5. annot.omit.only-code= pass is the only member line that changes generated code. Every other line changes only facts.
Error example
use std.structure.Structure

struct Audit:
    at: i64

struct Account:
    Audit
    id: i64
    token: Token

impl Show for Account by Structure:
    token = pass  # error: omitted-member-without-default
    Audit = pass  # error: omitted-member-without-default

An embedded field has no default syntax (data.default.embedded), so omitting one, as Audit = pass above, is always this error.

#Line Drift

  1. annot.line.drift.lintDerivation blocks for traits of one package on one type with differing member lines produce a lint finding on the later block. Warning: derivation-line-drift.
  2. annot.line.drift.lint.deriveA block beside another same-package traitโ€™s @derive produces the same lint finding when it contains any member line. Warning: derivation-line-drift.
  3. annot.line.drift.legalDifferent member lines stay legal: configuration may differ per direction, such as encoding and decoding.
use std.serde.{Deserialize, Serialize}
use std.structure.Structure

struct Session:
    id: i64
    cache: i64 = 0

impl Serialize for Session by Structure:
    cache = pass

impl Deserialize for Session by Structure:
    cache = pass   # the lines agree: no warning

Why. Most blocks of one library should agree, so a difference is worth a second look. Some differences are deliberate, as serde's skip_serializing shows.

#Derivation Blocks With No Trait

A derivation block with no trait writes shared metadata for one type, away from its declaration. It names no trait and derives nothing:

use std.structure.Structure

struct User:
    @max_len(80)
    name: string
    email: string

impl User by Structure:
    name += [min_len(1)]
    email = [max_len(320)]

Every derivation of User then sees max_len(80) and min_len(1) on name, and max_len(320) on email.

Each line names an existing direct member, as any member line does. An embedded field is named by its final type name, and members promoted through it are not direct members. The block cannot add, rename, remove, or change the type of a member.

  1. annot.no-trait.formimpl X by Structure:, written without a trait, is a derivation block with no trait. It writes metadata of X and implements nothing.
  2. annot.no-trait.not-inherentA block with no trait is not an inherent implementation: it declares no members of X.
  3. annot.no-trait.lines-onlyIts body holds only member lines. A method or an associated type in it is an error, reported on that member. Error: misplaced-derivation.
  4. annot.no-trait.no-omitIt writes only metadata, so an omit line f = pass in it is an error. Error: invalid-member-line.
  5. annot.no-trait.after-decoratorsIts lines apply to the values that a member's decorators attach: a += line appends after them, and a = line replaces them.
  6. annot.no-trait.selfIts Self lines apply the same way to the type-level facts that decorators before X attach.
  7. annot.no-trait.declaration-factsThe result is the declaration facts of X and its members: every derivation of X sees it.
  8. annot.no-trait.then-blocksA derivation block's member lines then edit those declaration facts, for that block only.
  9. annot.no-trait.moduleA derivation block with no trait must be declared in the module that declares X, as an inherent implementation must. One declared elsewhere is an error, reported on the block. Error: misplaced-derivation.
  10. annot.no-trait.localA derivation block with no trait in a local scope is an error, reported on the block: local declarations carry no metadata. Error: misplaced-derivation.
  11. annot.no-trait.targetX must be a struct or an enum. Any other target, including a newtype, is an error, reported on the block. Error: misplaced-derivation.
  12. annot.no-trait.byThe name after by in a header without a trait must be Structure, as trait.by.no-trait states.
  13. annot.no-trait.genericThe header must declare only the own type parameters of X's declaration, in order and without bounds, and apply X to them, as impl[T] Box[T] by Structure:.
  14. annot.no-trait.generic.renameThe header may rename the parameters, as impl[U] Box[U] by Structure: for struct Box[T]. Only their count, their order, and the absence of bounds are checked.
  15. annot.no-trait.generic.errorAny other header, such as impl Box[i32] by Structure: or impl[T < Hash] Box[T] by Structure:, is an error, reported on the block. Error: misplaced-derivation.
  16. annot.no-trait.uniqueA type has at most one derivation block with no trait. A second one is an error, reported on the second block. Error: overlapping-impl.
Error example
use std.structure.Structure

struct Token:
    text: string = ""

struct Account:
    id: i64
    token: Token = Token {}

type Meters(i64)

struct Box[T]:
    item: T

impl Account by Structure:
    token = pass  # error: invalid-member-line
    fn label(self) -> string:  # error: misplaced-derivation
        "account"

impl Meters by Structure  # error: misplaced-derivation

impl Box[i32] by Structure  # error: misplaced-derivation

impl Account by Structure  # error: overlapping-impl

Why. The block reuses the derivation block's header and member lines, so shared metadata needs no keyword of its own. An omit line is not metadata, because it changes generated code. One block per type keeps shared metadata in one place, so no line depends on the order of blocks.

See also: Member Metadata, Facts.

#Facts

A fact is an ordinary value attached to a type, member, or variant for derivations to read:

fn key_for(style: Style, m: Member) -> string:
    match m.facts.find::[Rename]():
        .Some(r) => r.name
        .None => style.prefix + m.name
  1. annot.fact.descriptiveFacts are descriptive: a fact changes no generated call. Only a template's own code reads it.
  2. annot.fact.type-level-decoratorA decorator before a struct or enum declaration attaches a type-level fact, as @style(prefix="user_") does.
  3. annot.fact.member-metadataA member's or variant's declaration facts are its member metadata. Those are the values that its decorators attach, in source order, as the type's derivation block with no trait edits them.
  4. annot.fact.payloadA payload member's declaration facts are the values of the decorators before its payload parameter.
  5. annot.fact.sharedDeclaration facts are seen by every derivation of the type. A derivation block's member lines edit them for that block only.
  6. annot.fact.eval.lazyA fact expression is evaluated once per program, at run time. A type-level fact is evaluated when the program first reads it, like a lazily initialized global. A member or variant fact is evaluated when a traversal first builds the handle (Member, VariantInfo) that carries it.
  7. annot.fact.eval.unreadA type-level fact that the program never reads is never evaluated. A member or variant fact whose handle is never built is never evaluated.
  8. annot.fact.requirement-freeA fact expression must be requirement-free, as defined for default values.
  9. annot.fact.valueA fact may hold any value that a global may hold.
  10. annot.fact.eval.panic-runtimeA panic while a fact or metadata expression is evaluated is a runtime panic at the read that started the evaluation. Its report names the fact and the original panic category. Panic: fact-evaluation-failed.
  11. annot.fact.eval.reentrantA fact or metadata expression whose evaluation reads the same fact again is a runtime panic at that second read, before the second evaluation starts. Its report names the fact. Panic: fact-evaluation-failed.
  12. annot.fact.readA template reads the type-level facts through T::facts(), and a member's or variant's facts through its handle's info.facts.
  13. annot.fact.defaultA template falls back to its own default when a fact is absent. An absent or foreign fact is never an error.
  14. annot.fact.unused-block-decorator.lintA decorator before impl ... by Structure: attaches an unread value and produces a lint finding on the decorator. Warning: unused-derivation-fact.
  15. annot.fact.unused-block-decorator.any-typeThis warning applies whatever the value's type, so a primitive or standard value, such as @"internal", gets it too.
  16. annot.fact.unused-block-decorator.fixThe warning offers a fix-it that moves the value into the block as a Self += [...] member line.
  17. annot.fact.duplicate-decoratorTwo decorators before one declaration whose type-level facts have one concrete type are an error, reported on the later decorator. Error: duplicate-fact.
Error example
use std.structure.Structure

@style(prefix="p_")
struct Plain:
    id: i64

@"internal"
struct Note:
    id: i64

struct Quiet:
    id: i64

impl Quiet by Structure:
    Self += [style(prefix="q_")]

struct Loud:
    id: i64

@style(prefix="l_")  # warning: unused-derivation-fact
impl Loud by Structure:
    id += ["key"]

struct Hidden:
    id: i64

@"internal"  # warning: unused-derivation-fact
impl Hidden by Structure:
    id += ["key"]

@style(prefix="a_")
@style(prefix="b_")  # error: duplicate-fact
struct Twice:
    id: i64
  1. annot.fact.block-on.directA call of std.task.block_on or println written directly in a fact or metadata expression is an error. Error: suspension-forbidden-context.
  2. annot.fact.block-on.indirectSuch a call reached through another call while the expression is evaluated panics, as req.drive.block-on.indirect says. Panic: suspension-forbidden-context.
Error example
use std.task.block_on

struct Style:
    prefix: string

fn ready!() -> string:
    "p_"

fn style_of[T](source: T) -> Style:
    Style { prefix: "p_" }

@style_of(block_on(ready()))  # error: suspension-forbidden-context
struct User:
    id: i64

Why. Configuration is data on the type, not a hook on the trait. So a derived trait gains no associated function, and two libraries' facts never collide.

Note. A fact expression may call a function in another file. The package interface records each fact's expression and type, and the running program computes the value on its first read. So checking a dependent never waits for the bodies a fact expression calls.

Note. The panic of an indirect block_on or println call happens while the program evaluates the fact. So it is reported as fact-evaluation-failed, by annot.fact.eval.panic-runtime, naming suspension-forbidden-context.

Why. Facts are runtime values so that the compiler needs no evaluator, no evaluation budget, and no cache of fact values. A fact that no derivation reads costs nothing.

#Walk, Describe, And Build

The compiler generates three traversals for each derivation. A struct is traversed as an enum with one variant.

#Walk

  1. annot.walk.matchGenerated walk matches the value once and calls w.variant(v) for the value's variant.
  2. annot.walk.membersIt then calls w.member(h, value) for each member of that variant, in declaration order.
  3. annot.walk.valueEach member value is read from self, so it has the member's declared type.
  4. annot.walk.errorThe first .Err that variant or member returns ends the walk, and walk returns it. Otherwise walk returns .Ok(()).
  5. annot.walk.nestedA member is passed as one value. Its own members are reached only through its own type's implementations.
  6. annot.walk.restFor a rest member, generated walk calls w.rest(h, items) in place of w.member, with the rest element's list as items.
  7. annot.walk.rest.defaultWalker's default rest calls self.member(h, items). So a walker that does not implement rest sees the rest member as one List[T] member.
@derive(Debug)
struct Point:
    x: i32
    y: i32

fn show(p: Point) -> string:
    debug(p)   # Point { x: 1, y: 2 }: the walk visits members in declaration order

#Describe

  1. annot.describe.orderGenerated describe calls d.variant(v) for every variant in declaration order, each followed by d.member(h) for that variant's members in declaration order.
  2. annot.describe.no-valuedescribe holds no value.
  3. annot.describe.errorThe first .Err ends the traversal and is returned, as for walk.
use std.structure.{Describer, Field, Structure, Variant}

struct NameList:
    pub names: List[string]

impl[T] Describer[T] for NameList:
    type Error = never
    fn variant(&mut self, v: Variant[T]) -> Result[void, never]:
        self.names.push(v.info.name)
        .Ok(())
    fn member[F](&mut self, h: Field[T, F]) -> Result[void, never]:
        self.names.push(h.info.name)
        .Ok(())

trait Fields:
    fn fields() -> List[string]

impl[T] Fields for T by Structure:
    fn fields() -> List[string]:
        let mut out = NameList { names: [] }
        _ := T::describe(&mut out)
        out.names

@derive(Fields)
enum Shape:
    Dot
    Pair(i32, i32)

fn demo() -> List[string]:
    Shape::fields()   # ["Dot", "Pair", "_0", "_1"]

#Build

  1. annot.build.variantGenerated build first calls s.variant(choices) once, with every variant in declaration order. A struct offers its one variant.
  2. annot.build.next-optionIt then calls s.next(members) with the chosen variant's members, repeatedly, until the source returns .Ok(.None).
  3. annot.build.memberFor each member key, it calls s.member(h, previous), where previous is the value already read for that member, or .None.
  4. annot.build.missing-after-noneAfter .Ok(.None), it calls s.missing(h) once for each member that the source never named.
  5. annot.build.constructIt then constructs the chosen variant from one local per member, and returns it.
  6. annot.build.errorsThe first .Err from a source method ends the build and is returned. Generated code never creates an error value of its own.
  7. annot.build.foreign-keyA key that does not name one of the chosen variant's members is a checked runtime panic with category structure-variant-mismatch.
  8. annot.build.sharedbuild never reads shared constructor data: the chosen variant's -> expression fixes it.
use std.ops.Default

@derive(Default)
struct Config:
    retries: i32
    name: string

fn demo() -> Config:
    Config::default()   # Config { retries: 0, name: "" }: built member by member

#Members And Variants

  1. annot.member.infoEach member has a Member value: its name, its zero-based position within its variant, its facts, its doc comment, and the embedded and positional flags.
  2. annot.member.embeddedAn embedded field is one member, named by its embedded type's final name, with embedded true.
  3. annot.member.positionalAn unnamed payload parameter is a member named _0, _1, and so on, by position, with positional true.
  4. annot.variant.documented-infoEach variant has a VariantInfo value: its name, zero-based index, facts, variant doc, type_doc, of_data, and shared.
  5. annot.variant.dataA struct's one variant has of_data true, and its name and doc comment are the type's.
  6. annot.variant.data-factsThat variant's facts is empty: a template reads the type-level facts once, through T::facts().
  7. annot.variant.shared-pairsshared holds the variant's shared constructor data as (name, value) pairs. An unnamed shared parameter is named _0, _1, and so on.
  8. annot.variant.shared.no-handleShared constructor data is never a member: it is never passed as a handle.
  9. annot.member.position-usizeMember.position has type usize.
  10. annot.variant.index-usizeVariantInfo.index has type usize.
  11. annot.member.documentationMember.doc is the attached declaration documentation normalized by lex.doc.attach, or .None when absent.
  12. annot.variant.documentationVariantInfo.doc is the variant's attached declaration documentation, or .None when absent.
  13. annot.variant.type-documentationEvery variant of a declared type carries that type's attached documentation in type_doc, or .None when absent.
  14. annot.variant.type-documentation-sameEvery variant of the same type carries the same type_doc.
  15. annot.variant.type-documentation-defaultA manually constructed VariantInfo defaults type_doc to .None.
use std.structure.{Describer, Field, Structure, Variant}

struct Info:
    pub lines: List[string]

impl[T] Describer[T] for Info:
    type Error = never
    fn variant(&mut self, v: Variant[T]) -> Result[void, never]:
        self.lines.push("variant ${v.info.index}: ${v.info.name}")
        .Ok(())
    fn member[F](&mut self, h: Field[T, F]) -> Result[void, never]:
        self.lines.push("member ${h.info.position}: ${h.info.name}")
        .Ok(())

trait Report:
    fn report() -> List[string]

impl[T] Report for T by Structure:
    fn report() -> List[string]:
        let mut out = Info { lines: [] }
        _ := T::describe(&mut out)
        out.lines

@derive(Report)
enum Shape:
    Dot
    Pair(i32, i32)

fn demo() -> List[string]:
    Shape::report()
    # ["variant 0: Dot", "variant 1: Pair", "member 0: _0", "member 1: _1"]

Note. An embedded member is passed as one value of its embedded type. The language never flattens an embedded field; a library that flattens reads the embedded flag.

See also: Data Embedding, Shared Enum Constructor Data.

#Tuple Structure

A tuple type has a Structure for its tuple templates. It is traversed as a struct whose members are its elements:

fn show(t: (i32, i32, List[i32]...)) -> string:
    debug(t)
Part For (i32, i32, List[i32]...)
T::name() ""
the one variant of_data true, named ""
members _0: i32, _1: i32, and the rest member _2: List[i32]
  1. annot.tuple.structureThe compiler generates Structure for a tuple type only while it instantiates a tuple template for that type.
  2. annot.tuple.variantA tuple's one variant has index 0, of_data true, the name "", no facts, no doc comment, and no shared data.
  3. annot.tuple.membersIts members are its elements in order, named _0, _1, and so on, with positional true, embedded false, no facts, and no doc comment.
  4. annot.tuple.nameFor a tuple type, T::name() is "" and T::facts() is empty.
  5. annot.tuple.restA tuple with a rest element List[T]... has one rest member for it: its last member, of type List[T].
  6. annot.tuple.self-refEvery member of a tuple and its variant have self_ref .Absent, since a tuple type has no declaration to refer to.
  7. annot.tuple.no-defaultNo member of a tuple has a default.
  8. annot.tuple.buildGenerated build constructs the tuple from one value per member. The rest member's list becomes the rest element's items.
  9. annot.tuple.no-type-documentationA tuple's variant has type_doc .None.

Why. One List member keeps a rest tuple's Structure the same size for every value, as its type is. A walker that needs the items one by one, as text does, implements rest.

#Self References

A member's or variant's self reference, its self_ref, tells a template whether the member's type refers to the type being derived. It also tells whether the member's type needs that type. Both follow from the member's declared type alone, by the rules below.

  1. annot.self-ref.enclosingThe enclosing type of a member or a variant is the struct or enum that declares it.
  2. annot.self-ref.enclosing.argumentsIn these rules, the enclosing type is its declaration applied to any type arguments. Inside enum Nest[T], Nest[List[T]] is the enclosing type.
  3. annot.self-ref.refersA type refers to the enclosing type when it is that type, or has a type argument or a tuple element that refers to it.
  4. annot.self-ref.refers.membersA struct or enum also refers to the enclosing type when one of its members has a type that refers to it.
  5. annot.self-ref.refers.referenceA reference type, *T, *mut T, &T, or &mut T, refers to the enclosing type when T does.
  6. annot.self-ref.needs.selfA type needs the enclosing type when it is the enclosing type.
  7. annot.self-ref.needs.compoundA tuple or struct needs it when one of its element or member types needs it, with the struct's type arguments substituted.
  8. annot.self-ref.needs.resultResult[T, E] needs it when T does.
  9. annot.self-ref.needs.referenceA reference type, *T, *mut T, &T, or &mut T, needs it when T does, since a reference always refers to a value.
  10. annot.self-ref.needs.enumAny other enum type needs it when each of its variants has a member whose type needs it, with the enum's type arguments substituted.
  11. annot.self-ref.needs.stopA List, Map, or optional type never needs the enclosing type, whatever its type arguments.
  12. annot.self-ref.needs.onlyNo other type needs the enclosing type.
  13. annot.self-ref.memberA member's self_ref is .Required when its type needs the enclosing type, .Optional when its type refers to it without needing it, and .Absent otherwise.
  14. annot.self-ref.variantA variant's self_ref is the strongest of its members' values, where .Required is stronger than .Optional and .Optional than .Absent.
  15. annot.self-ref.variant.emptyA variant with no members has self_ref .Absent.
  16. annot.self-ref.variant.omittedA variant's self_ref also counts every member that the derivation omits with = pass, so it reflects the declaration rather than one derivation's view.
enum Expr:
    Num(value: i32)
    Add(left: *Expr, right: *Expr)
    Block(items: List[Expr])
    Wrap(inner: Inner)
    Pick(choice: Choice)

enum Inner:
    Only(expr: *Expr)

enum Choice:
    Skip
    Take(expr: *Expr)

struct Node:
    label: string
    parent: *Node?
Variant or member self_ref
Num and value; label .Absent
Add, left, and right .Required
Wrap and inner, since Inner's every variant needs Expr .Required
Pick and choice, since Choice's Skip does not .Optional
Block and items; Node's one variant and parent .Optional

Why. One enum, not two flags. A type that needs the enclosing type also refers to it, so two independent flags would allow a state that cannot occur.

Note. In struct Node with parent: &Node? and a block line parent = pass, the one variant of Node still has self_ref .Optional, although its derivation walks only label.

Note. Templates other than Arbitrary may read self_ref too, as library design. When any self_ref is not .Absent, a codec may add a nesting-depth limit, and a schema generator may emit a named definition with a $ref. Debug may truncate deep output. The archived STDLIB draft sketches these.

#Handles

A handle is a compiler-generated constant that names one member or variant of a derivation's target.

  1. annot.handle.constantsEach member has one constant handle of type Field[S, F], and each variant one of type Variant[S]. A handle refers to a record that lives as long as the program.
  2. annot.handle.member-typewalk, describe, and build pass handles whose F is the member's declared type: for hits: *mut Counter, it is *mut Counter.
  3. annot.handle.field-varianceField is invariant in S and F, and Variant, Members, and Key are invariant in S, as a counted reference is in its referent (ref.type.kind-distinct).
  4. annot.handle.geth.get(x) accepts any S. Its type is the type that the field access x.member would have, for a member declared F.
  5. annot.handle.get.varianth.get(x) on a payload member, when x holds another variant, is a checked runtime panic with category structure-variant-mismatch.
  6. annot.handle.holdsv.holds(x) is true exactly when x holds the variant v.
  7. annot.handle.defaulth.has_default() is true when the member declares a default. h.default() evaluates that default, or returns .None when there is none.
  8. annot.handle.escapeHandles are ordinary values and may escape the traversal that passed them.
  9. annot.handle.fact.typedh.fact::[D]() reads the member's fact of the typed fact type D, or .None.
  10. annot.handle.fact.patternIt matches D's pattern against the handle's F exactly as the attach-time check does, by annot.typed-fact.infer. Its type is D[A]?, where A holds the inferred arguments.
  11. annot.handle.fact.pattern.exampleSo under @annotate::[List[T]](.Field) struct MaxLen[T], h.fact::[MaxLen]() on the handle of tags: List[string] has type MaxLen[string]?.
  12. annot.handle.fact.pattern.mismatchAn F that does not fit D's pattern is an error, as it is at attach time. Error: type-mismatch.
  13. annot.handle.fact.pattern.writtenD may be written with its type arguments, as in h.fact::[Fallback[F]](). They must be the arguments that the match infers. Error: type-mismatch.
  14. annot.handle.fact.exactIt finds only a fact whose type is exactly D[A].
  15. annot.handle.fact.keyThe type parameters that D[A] mentions follow annot.structure.find-key, except the handle's own F, which needs no runtime identity. Error: unsatisfied-trait-bound.
  16. annot.handle.shared-targetTraversals using the same Structure at the same concrete target type share its constant handles.
struct Counter:
    count: i64

@derive(Duplicate)
struct Stats:
    hits: *mut Counter
    label: string

struct CopySource[S]:
    old: S

impl[S] Source[S] for CopySource[S]:  # variant, next, and member elided
    type Error = never

    fn missing[F](&mut self, h: Field[S, F]) -> Result[F, never]:
        .Ok(h.get(self.old))

Note. A walker that holds a second value, such as a diff, checks v.holds(other) in variant before any h.get(other). The standard-library Clone trait, listed in STDLIB, declares clone(self), whose source reads each member from the value, as CopySource does.

Note. Handles are constants, and each generated call passes a constant dictionary, so a traversal allocates nothing per member.

Why. The handle already fixes M's argument for its target parameter to F, so the lookup matches that part without a runtime key. A walker's member[F] therefore reads a typed fact with no extra bound.

#Walkers, Describers, And Sources

A walker, describer, or source is an ordinary type that implements Walker, Describer, or Source:

impl[S] Walker[S] for Encoder:
    type Error = never

    fn variant(&mut self, v: Variant[S]) -> Result[void, never]:
        if !v.info.of_data:
            self.out = self.out + self.style.tag + "=" + v.info.name + ";"
        .Ok(())

    fn member[F < Encode](&mut self, h: Field[S, F], value: F) -> Result[void, never]:
        self.out = self.out + key_for(self.style, h.info) + "=" + value.encode() + ";"
        .Ok(())
  1. annot.walker.strengthen-memberAn implementation of Walker, Describer, or Source may strengthen the bound on member[F], as F < Encode above.
  2. annot.walker.strengthen-restAn implementation of Walker may implement rest and strengthen the bound on its T, as rest[T < Display] does.
  3. annot.walker.missing-fixedA Source implementation must not strengthen the bound on missing[F]: it keeps the trait's unbounded F.
  4. annot.walker.missing-fixed.errorA strengthened bound on missing[F] is an error, reported at the implementation method. Error: trait-method-signature.
  5. annot.walker.not-sealedWalker, Describer, and Source are not sealed. Any package may implement them.
  6. annot.walker.obligationEvery member that a derivation walks, describes, or builds must satisfy the strengthened bounds of the walker, describer, or source that the template passes.
  7. annot.walker.obligation.restFor a rest member, a walker that implements rest puts the obligation on the item type T, by rest's bound. Otherwise the obligation is on List[T], by member's bound.
  8. annot.walker.obligation.errorThe obligation is checked at the opt-in. A member that fails it is an error, reported at the opt-in and naming the member. Error: member-not-derivable.
  9. annot.walker.generic-member-callmember may be called through a generic walker, describer, or source type only by generated code. Such a call written in source is an error. Error: generic-member-call.
  10. annot.walker.generic-rest-callrest may likewise be called through a generic walker type only by generated code and by its own default body. Any other such call is an error. Error: generic-member-call.
  11. annot.walker.generic-missingCode outside generated code may call missing through a generic source type.
  12. annot.walker.concrete-callA call on a concrete walker, describer, or source type applies that type's own bounds.
Error example
@derive(Show)  # error: member-not-derivable
struct Label:
    text: string

fn forward[S, W < Walker[S]](w: &mut W, h: Field[S, i64], value: i64) -> void:
    _ := w.member(h, value)  # error: generic-member-call

fn fill[S, F, R < Source[S]](r: &mut R, h: Field[S, F]) -> Result[F, R::Error]:
    r.missing(h)  # valid: `missing` may be called through a generic source

impl[S] Source[S] for Strict:  # variant, next, and member elided
    type Error = never

    fn missing[F < Display](&mut self, h: Field[S, F]) -> Result[F, never]:  # error: trait-method-signature
        panic("missing ${h.info.name}")

Why. A strengthened bound becomes one obligation per member, checked where both the member types and the walker are known. A generic call could bypass that check. The bound on missing stays fixed, so a generic missing call is always checked against the trait's own signature.

#Implementation Facts

A fact on a source implementation tells generated build which members the source never receives:

use std.structure.{Source, Variant, Field, Members, Key, fills_defaults}

trait Zero:
    fn zero() -> Self

struct Zeroes[S]:
    pub at: usize

@fills_defaults()
impl[S] Source[S] for Zeroes[S]:  # variant, next, and missing elided
    type Error = never

    fn member[F < Zero](&mut self, h: Field[S, F], previous: F?) -> Result[F, never]:
        .Ok(F::zero())

A template that passes Zeroes to build gives a member written label: Name = Name { text: "none" } its declared default, so Name need not implement Zero.

  1. annot.impl-fact.fills-defaults.declstd.structure declares the fact type FillsDefaults, limited to implementations by @annotate(.Impl), and fills_defaults() -> FillsDefaults. The compiler recognizes std.structure.fills_defaults by its qualified name.
  2. annot.impl-fact.fills-defaultsWhen a template's build call passes a source whose implementation carries fills_defaults(), generated build fills each member that declares a default from that default. It never offers such a member to the source.
  3. annot.impl-fact.fills-defaults.exemptSuch a member's type need not satisfy that source's bounds.
  4. annot.impl-fact.fills-defaults.scopeThe fact changes only that derivation. Every other derivation of the type still walks, describes, and builds the member.
  5. annot.impl-fact.fills-defaults.otherOn any other implementation, fills_defaults() changes nothing.

A walker, describer, or source may also name a field fact whose members it never receives:

use std.annotation.annotate
use std.structure.{Walker, Variant, Field, skips}

@annotate(.Field)
struct Skip: pass

fn skip() -> Skip:
    Skip {}

@skips::[Skip]()
impl[S] Walker[S] for Writing:  # variant elided
    type Error = never

    fn member[F < Write](&mut self, h: Field[S, F], value: F) -> Result[void, never]:
        self.out = self.out + value.write()
        .Ok(())

A template that passes Writing never sees a member marked @skip(), so that member's type need not implement Write.

  1. annot.impl-fact.skips.declstd.structure declares the fact type Skips[F], limited to implementations by @annotate(.Impl), and skips[F]() -> Skips[F]. The compiler recognizes std.structure.skips by its qualified name and reads its type argument.
  2. annot.impl-fact.skipsWhen a template passes a walker, describer, or source whose implementation carries skips::[F](), a member whose known facts hold a value of type F is omitted. It is omitted as = pass omits it, by annot.omit.skip and annot.omit.default.
  3. annot.impl-fact.skips.knownA member's known facts are its decorators' values and the elements of the list literals its member lines write. A fact that a named list or a spread brings is not known, as annot.line.duplicate states.
  4. annot.impl-fact.skips.exemptAn omitted member's type need not satisfy any bound of that derivation's walkers, describers, or sources.
  5. annot.impl-fact.skips.scopeThe omission changes only that derivation. Every other derivation of the type still walks, describes, and builds the member.
  6. annot.impl-fact.skips.no-defaultIn a derivation whose template calls build, an omitted member that declares no default is an error, reported at the opt-in and naming the member. Error: omitted-member-without-default.

Why. The source knows that it would answer a defaulted member with its default anyway. Saying so on the source keeps that knowledge in the library, instead of a compiler rule about one trait.

See also: Omitted Members.

#Derived Bounds

  1. annot.bound.paramsA derived implementation for a generic type gets T < Trait for each type parameter T that appears in a walked, described, or built member.
  2. annot.bound.declaredA derived implementation also keeps every bound the type declares on its parameters. So @derive(Eq) struct Pair[T < Eq & Debug] derives impl[T < Eq & Debug] Eq for Pair[T].
  3. annot.bound.omittedAn omitted member contributes no bound.
  4. annot.bound.recursiveRecursion is checked coinductively: while checking the members of Tree[T], its own derived implementation is assumed to hold.
  5. annot.bound.moreWhen a member needs more than T < Trait, as a Map[T, V] member needs T < Hash, the error suggests a derivation block whose header states the bounds. Error: member-not-derivable.
@derive(Eq, Debug)
struct Box[T]:
    value: T

fn same(a: Box[i32], b: Box[i32]) -> bool:
    a == b   # the derived implementation has T < Eq, and i32 meets it

Note. A derived bound names the trait, as T < Encode. When a walker's strengthened bound asks for more, that bound is the obligation checked at the opt-in, and the diagnostic names it.

Note. The stdlib's typed CLI records use the same strengthened member bounds to require Cli at a derive. Their typed Short[F < Cli] field fact also requires a CLI-readable field. The typed Positional[F < Cli] field fact has the same bound.

See also: trait.derive.bounds, Derived Newtypes.

#Limits

  1. annot.limit.in-placeThere is no in-place traversal: generated code never assigns a member of an existing value. A derived merge returns a new value.
  2. annot.limit.interfacesA package interface carries each template's body, the walker, describer, and source bodies it names, and the functions its facts call.
  3. annot.limit.specializeEach (target, walker) pair is one specialization of the generated traversal.
use std.ops.Default

@derive(Default)
struct Cart:
    items: List[string]

fn demo() -> Cart:
    Cart::default()   # a new value: a traversal never edits an existing one

Note. A template declares no compile-time constants of its own, and shared constructor data has no typed handles. A wrapper walker cannot forward to a generic inner walker's member, as annot.walker.generic-member-call states.

Note. Wire stability is documented, not checked. Reordering or renaming members or variants changes derived formats, VariantInfo.index, and derived Ord. A library may require explicit tags as facts.

#Undecided Parts

These parts of typed derivation are open design questions, listed in the open-issues umbrella. An implementation must not guess them:

Part State
Non-escaping handles Whether a future non-escaping trait design makes handles non-escaping.
Clone's module Which standard module declares Clone. It is chosen with the standard library.
Derived-function cache The API of the standard cache for derived associated functions. It is chosen with the standard library.
Function targets Deriving for functions, as tool adapters need (parked tool adapters). A decorator before a function attaches a value, as Prefix Decorators defines.

#Serialization

A type opts into serialization for every format at once, to write its values or to build them, private members included, by deriving or implementing two traits of std.serde:

use std.serde.{Serialize, Deserialize}

@derive(Serialize, Deserialize)
struct Session:
    pub user: string
    token: string
    expires_at: i64
  1. annot.serde.definitionA type opts into serialization when it implements std.serde.Serialize, and into deserialization when it implements std.serde.Deserialize.
  2. annot.serde.private-library-viewThe opt-in covers private members, as annot.structure.private allows.
  3. annot.serde.derivablestd.serde declares the template of each trait. So @derive(Serialize), @derive(Deserialize), and a derivation block for either trait are valid.
  4. annot.serde.membersEvery member that a derived Serialize walks must implement Serialize, and every member that a derived Deserialize builds must implement Deserialize.
  5. annot.serde.members.errorA member that does not is an error, reported at the opt-in and naming the member. Error: member-not-derivable.
  6. annot.serde.by-handA type may implement either trait by hand instead. A written implementation opts in too.
  7. annot.serde.no-dynNo format selects a type from the data: deserialization never constructs a polymorphic or dyn value from a payload. A payload that names its own type is data, not instructions: Java's gadget chains show where trusting it leads.
Error example
use std.serde.Serialize

struct Secret:
    value: i32

@derive(Serialize)  # error: member-not-derivable
struct Account:
    id: i32
    secret: Secret

Why. Rust's serde and Swift's Codable work this way: a type opts in once, and each format is a writer or reader that the type's implementation drives. Adding a format then needs no change to any type.

Note. A format declares no trait of its own for types to implement, so a type has exactly one serialized form for all formats. A derivation block's member lines, such as cache = pass, configure that one form. Per-format overriding is open, in Serialization Formats.

Note. Document member names may be configured by the ordinary std.serde.Rename field fact. It adds no language annotation form.

See also: Boundary Encoding, Serde, Typed JSON.

#The std.serde Module

The module std.serde declares the two serialization traits and the format protocol they drive:

pub trait Serialize:
    fn serialize[W < Serializer](self, out: &mut W) -> Result[void, W::Error]

pub trait Deserialize:
    fn deserialize[R < Deserializer](input: &mut R) -> Result[Self, R::Error]

pub trait Serializer:
    type Error
    fn null(&mut self) -> Result[void, Self::Error]
    fn bool(&mut self, value: bool) -> Result[void, Self::Error]
    fn int(&mut self, value: i64) -> Result[void, Self::Error]
    fn uint(&mut self, value: u64) -> Result[void, Self::Error]
    fn float(&mut self, value: f64) -> Result[void, Self::Error]
    fn text(&mut self, value: string) -> Result[void, Self::Error]
    fn begin_list(&mut self, len: usize) -> Result[void, Self::Error]
    fn end_list(&mut self) -> Result[void, Self::Error]
    fn begin_map(&mut self, len: usize) -> Result[void, Self::Error]
    fn key(&mut self, key: string) -> Result[void, Self::Error]
    fn end_map(&mut self) -> Result[void, Self::Error]
    fn begin_variant(&mut self, facts: Facts, v: VariantInfo) -> Result[void, Self::Error]
    fn member(&mut self, m: Member) -> Result[void, Self::Error]
    fn end_variant(&mut self) -> Result[void, Self::Error]

pub enum ValueKind:
    Null
    Bool
    Int
    Uint
    Float
    Text
    List
    Map

pub trait Deserializer:
    type Error
    fn peek(&mut self) -> Result[ValueKind, Self::Error]
    fn is_null(&mut self) -> Result[bool, Self::Error]
    fn bool(&mut self, expected: string) -> Result[bool, Self::Error]
    fn int(&mut self, expected: string) -> Result[i64, Self::Error]
    fn uint(&mut self, expected: string) -> Result[u64, Self::Error]
    fn float(&mut self, expected: string) -> Result[f64, Self::Error]
    fn text(&mut self, expected: string) -> Result[string, Self::Error]
    fn begin_list(&mut self) -> Result[void, Self::Error]
    fn next_item(&mut self) -> Result[bool, Self::Error]
    fn begin_map(&mut self) -> Result[void, Self::Error]
    fn next_key(&mut self) -> Result[string?, Self::Error]
    fn begin_variant(&mut self, facts: Facts, choices: List[VariantInfo]) -> Result[usize, Self::Error]
    fn member(&mut self, m: Member) -> Result[void, Self::Error]
    fn end_variant(&mut self) -> Result[void, Self::Error]
    fn invalid(&mut self, expected: string) -> Self::Error
    fn is_absent(&mut self) -> bool:
        false
  1. annot.serde.moduleThe standard module std.serde declares Serialize, Deserialize, Serializer, Deserializer, and ValueKind, as above. Facts, VariantInfo, and Member are those of std.structure.
  2. annot.serde.knownThe compiler knows Serialize and Deserialize by name, for the boundary rule module.boundary.out. It knows no other part of std.serde.
  3. annot.serde.not-sealedNone of the five items is sealed. Any package may implement the four traits.

Why. Structure may be named only inside a template, so a format cannot walk a type itself. The derived templates walk it once, for every format, and pass each member to the format's writer or reader.

Note. The meaning of each Serializer and Deserializer call, the templates, and the standard implementations are library behavior, in Serde.

#Error Derivation

Error derivation implements Display, std.error.Error, and From for an error type from its @error lines. It is Rust's thiserror in hd: messages, @from, @source, and transparent errors.

use std.error.Error

struct YamlError:
    line: i64

impl Display for YamlError:
    fn to_string(self) -> string:
        "bad yaml at line ${self.line}"

impl Error for YamlError

@error
enum FsError:
    @error("not found: $path")
    NotFound(path: string)
    @error("permission denied: $_0")
    Denied(string)
    Busy

@error
enum LoadError:
    @error("cannot read $path")
    Read(path: string, @source error: FsError)
    @error("bad config")
    Yaml(@from error: YamlError)
    @error(transparent)
    Fs(@from error: FsError)

@error("config $name is missing")
struct MissingConfig:
    name: string

fn parse(text: string) -> Result[i64, YamlError]:
    .Err(YamlError { line: 1 })

fn load(text: string) -> Result[i64, LoadError]:
    value := parse(text)?
    .Ok(value)

load converts a YamlError through the generated impl From[YamlError] for LoadError. FsError.Busy displays as Busy.

#Error Types

  1. annot.error.intrinsic@error is a compiler intrinsic, as @derive is. It is not an ordinary decorator and attaches no value.
  2. annot.error.name@error always means the intrinsic. A binding named error in scope, such as an import, does not change it.
  3. annot.error.typeAn error type is an enum with a bare @error line, or a struct with an @error("...") or @error(transparent) line.
  4. annot.error.generatesFor an error type E, the compiler generates impl Display for E, impl Error for E, and one impl From[P] for E for each @from member.
  5. annot.error.ordinaryThe generated implementations are ordinary implementations, under the ordinary rules for coherence and overlap.
  6. annot.error.hand-writtenA hand-written implementation of Display, Error, or a generated From[P] for an error type is an error, reported on that implementation. Error: overlapping-impl.

The forms are:

Rule Form Written before Meaning
annot.error.form.enumError enum @error an enum makes the enum an error type
annot.error.form.variant-messageVariant message @error("...") a variant of an error enum the variant's message
annot.error.form.variant-transparentTransparent variant @error(transparent) a variant of an error enum with one payload member forwards the message and the cause to that member
annot.error.form.data-messageError struct @error("...") a struct makes it an error type with that message
annot.error.form.data-transparentTransparent struct @error(transparent) a struct with one field makes it an error type that forwards to that field
annot.error.form.fromConversion marker @from the only payload parameter of a variant of an error enum, or the only field of an error struct generates From and marks the cause
annot.error.form.sourceCause marker @source one payload parameter of a variant of an error enum, or one field of an error struct marks the cause
  1. annot.error.form.otherAny other @error line is an error, and so is any other @from or @source line inside an error type. A form on the wrong target is decorator-target-kind; an invalid argument or marker argument is invalid-error-marker, as the rules below specify.
  2. annot.error.form.misplacedA form written before a target that the table does not list for it is an error, reported on the form. Error: decorator-target-kind.
  3. annot.error.form.misplaced.examplesSo @error before a function or a newtype, and a bare @error before a struct, are each decorator-target-kind. So are @error("...") before an enum, and @from beside a second payload member.
  4. annot.error.form.argumentAn @error line whose arguments are neither one message nor transparent is an error, reported on the line. Error: invalid-error-marker.
  5. annot.error.form.argument.examplesSo @error(opaque), @error(42), and @error("closed", "shut") are each invalid-error-marker.
  6. annot.error.form.marker-argumentAn @from or @source line with arguments inside an error type, such as @from(yaml), is an error, reported on the line. A marker takes no value. Error: invalid-error-marker.
  7. annot.error.markerInside an error type, from, source, and transparent in these forms are markers, not names. A binding with the same name does not change them.
  8. annot.error.marker.no-valueA marker attaches no value, so it is neither member metadata nor a fact.
  9. annot.error.marker.outsideOutside an error type, @from and @source have no special meaning: each is an ordinary decorator.
  10. annot.error.no-useWriting @error, @from, or @source needs no use std.error.Error, as annot.derive.no-use needs none for Structure. Code that names Error itself still imports it, as trait.error.import states.
Error example
@error
enum ReadError:
    @error("closed")
    Closed

impl Display for ReadError:  # error: overlapping-impl
    fn to_string(self) -> string: "read error"

enum Plain:
    Wrap(@from error: ReadError)  # error: unknown-name

@error("no tool")  # error: decorator-target-kind
fn tool() -> string:
    "hammer"

@error
enum WriteError:
    @error(opaque)  # error: invalid-error-marker
    Full
    @error("read failed")
    Read(@from(strict) error: ReadError)  # error: invalid-error-marker

Why. Typed derivation generates one implementation per trait. An error type needs one From per marked variant, each a different instantiation, and Display, Error, and From all come from the same markers. One intrinsic generates them together.

Why. Error derivation has no error codes. Inside a program the variant is the code: code finds it with find::[T]() and matches it. Codes for logs and APIs belong to a boundary-safe report type.

#Error Messages

  1. annot.error.message.stringA message is an ordinary interpolated string, as String Interpolation defines. A prefixed string is not a message, since it would run a user function each time the error is shown. Error: invalid-error-marker.
  2. annot.error.message.scopeA variant's message sees the variant's named payload members by name, and its unnamed payload members as _0, _1, and so on.
  3. annot.error.message.unnamedUnnamed payload members are numbered from zero, in declaration order.
  4. annot.error.message.sharedA variant's message also sees the enum's named shared fields by name.
  5. annot.error.message.shared-unnamedUnnamed shared data is not in scope, so $_0 and $_1 always name the variant's unnamed payload members.
  6. annot.error.message.dataA struct's message sees the struct's fields by name.
  7. annot.error.message.checkedA message is type-checked as ordinary code in that scope. A name that resolves to nothing is an error. Error: unknown-name.
  8. annot.error.message.displayEach interpolated value must implement Display, as expr.interp.no-display requires. Error: unsatisfied-trait-bound.
  9. annot.error.message.no-selfA message is not inside a method, so self names nothing there, and $self in a message is an error. Error: unknown-name.
  10. annot.error.message.evalThe generated to_string evaluates the value's message each time it is called, with the value's members bound.
  11. annot.error.message.absentA variant without a message displays as its variant name, as FsError.Busy displays as Busy.
Error example
struct Span:
    start: i64

@error
enum ParseError:
    @error("bad token $token")  # error: unknown-name
    BadToken(text: string)
    @error("bad span $span")  # error: unsatisfied-trait-bound
    BadSpan(span: Span)
    @error("bad $self")  # error: unknown-name
    Other

@error
enum HttpError(i32):
    @error("denied: $_0")
    Denied(string) -> HttpError(403)
    @error("status $_0")  # error: unknown-name
    Missing -> HttpError(404)

In HttpError, $_0 names Denied's payload member, never the shared status code, so Missing has no _0 in scope.

Why. $self would call the generated to_string from inside itself and never finish.

#Error Causes

  1. annot.error.source.reference-resultThe generated Error.source returns a shared reference to the cause member, or .None when it has none.
  2. annot.error.cause.memberA variant's or struct's cause member is its @from or @source member.
  3. annot.error.cause.explicitA variant or struct without @from or @source has no cause. There is no automatic cause and no field-name convention.
  4. annot.error.cause.oneA variant or struct has at most one @from or @source member. A second one is an error, reported on that member. Error: invalid-error-marker.
  5. annot.error.cause.typeA cause member's type must implement Error. Error: unsatisfied-trait-bound.
  6. annot.error.cause.optionalA @source member may instead have the type E?, where E implements Error. A .None member gives no cause.
Error example
@error("disk full")
struct DiskError:
    free: i64

@error
enum SaveError:
    @error("cannot save $path")
    Write(path: string, @source error: DiskError?)
    @error("save failed")
    Other(@source error: DiskError)
    @error("two causes")
    Both(@source first: DiskError, @source second: DiskError)  # error: invalid-error-marker
    @error("bad path")
    Path(@source reason: i64)  # error: unsatisfied-trait-bound

#Error Conversions

  1. annot.error.from.generate@from on a variant's only payload member of type P generates impl From[P] for E, whose from builds that variant.
  2. annot.error.from.data@from on an error struct's only field generates the same implementation, whose from builds the struct value.
  3. annot.error.from.unmarkedA member without @from generates no From, so Invalid(reason: string) never yields From[string].
  4. annot.error.from.same-typeTwo @from members of one type P generate overlapping implementations of From[P]. The later one is an error, reported on its member. Error: overlapping-impl.
  5. annot.error.from.type-parameterA @from member whose type is a bare type parameter is an error. Error: invalid-error-marker.
Error example
@error("bad yaml")
struct YamlError:
    line: i64

@error
enum RuleError:
    @error("bad utils")
    Utils(@from error: YamlError)
    @error("bad rule")
    Rule(@from error: YamlError)  # error: overlapping-impl

@error
enum AppError[E]:
    @error("inner failed")
    Inner(@from error: E)  # error: invalid-error-marker

Why. impl[E] From[E] for AppError[E] would overlap every other From of AppError, as a generic #[from] field does in Rust. Code converts such an error explicitly, as in map_err(AppError.Inner).

#Transparent Errors

  1. annot.error.transparent.displayA transparent variant or struct displays as its one member displays.
  2. annot.error.transparent.sourceIts source returns that member's own source, so a chain does not repeat the inner message.
  3. annot.error.transparent.typeThe member's type must implement Error. Error: unsatisfied-trait-bound.
@error
enum RepoError:
    @error("missing $_0")
    Missing(string)

@error(transparent)
pub struct PublicError:
    @from
    repr: RepoError

PublicError hides RepoError behind a stable public type, and ? converts a RepoError into it.

Note. Because a transparent value's source skips the inner error, find::[Inner]() does not see that inner error itself, as in Rust.

#Generated Error Bounds

  1. annot.error.bound.displayFor a generic error type, the generated Display gets P < Display for some type parameters. That is each P that is the type of an interpolated member or a transparent member.
  2. annot.error.bound.errorThe generated Error gets P < Error for each type parameter P that is the type of a @from or @source member.
  3. annot.error.bound.display-carriedThe generated Display gives no bound to a type parameter that is only carried.
  4. annot.error.bound.transparent-sourceThe generated Error gets P < Error for each type parameter P that is the type of a transparent member, so its source can forward.
  5. annot.error.bound.carriedThe generated Error gets P < Inspectable for each type parameter P that is only carried. Such a P is the type of no interpolated, transparent, @from, or @source member.
  6. annot.error.bound.interpolatedThe generated Error gets P < Display & Inspectable for each type parameter P that only interpolated members have as their type. That is, an interpolated member has the type P, and no transparent, @from, or @source member does.
@error
enum TaskError[E, T]:
    @error("task $name failed")
    Failed(name: string, @source error: E, input: T)

@error
enum Wrapped[P]:
    @error(transparent)
    Inner(error: P)

@error
enum Named[T]:
    @error("got $value")
    Got(value: T)

TaskError[E, T] gets impl[E < Error, T < Inspectable] Error for TaskError[E, T], and its Display needs no bound on E or T. Wrapped[P] gets impl[P < Display] Display for Wrapped[P] and impl[P < Error] Error for Wrapped[P]. Its transparent member has no @from, since a @from member of a bare type parameter is invalid. Named[T] gets impl[T < Display] Display for Named[T] and impl[T < Display & Inspectable] Error for Named[T].

Why. An impl Error needs an inspectable target, as trait.error.not-inspectable states, and an unbounded type parameter is not inspectable. Display needs no such bound, so it keeps none.

See also: Error Trait, Conversion Trait, Propagation.