#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:
- attached values are ordinary typed values;
- derived behavior is requested explicitly, with
@derive,@error, or a derivation block; - runtime information is produced explicitly when requested;
- 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() -> Validatorread withUser::validator(). The earlier facet protocol (Annotation,Annotate, the annotator traits,AnnotationRef, andannotate Facet for Target) was removed by Typed Derivation decision 10 (2026-09-27). Building such a value once per type, with deferredRef[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 wordannotatewere 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)- annot.decorator.targetsA decorator may precede an item: a module-level function, struct, enum, trait, implementation, or newtype declaration.
- 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.
- annot.decorator.attachA decorator before a function, trait, implementation, newtype, or method attaches its value to that declaration and changes nothing else about it.
- 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. - annot.decorator.no-moduleA module takes no decorator, because it has no declaration.
- annot.decorator.function-deriveA
@derive(...)line before a module-level function declaration is an error, reported on the decorator. Error:decorator-not-annotator. - annot.decorator.derive-targetsA
@derive(...)line before a trait, an implementation, or a method is the same error. Error:decorator-not-annotator. - 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. - annot.decorator.facts-ofCode reads a value attached to a module-level function
fthroughfacts_of(f).find::[M](), as Function Facts defines. - annot.decorator.no-listingNo operation lists the declarations that carry a value, so code cannot enumerate every function with a given decorator.
@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:
passA 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]()- annot.decorator.bare-call-defaultsIn a decorator, a bare name that resolves to a function with no parameters without a default is called, so
@hiddenmeans@hidden(). - 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)- annot.inline.declared
std.annotationdeclares the enumInlineHint, with the variantsAuto,Always, andNever, the functioninline(hint: InlineHint = .Auto), the structCold, and the functioncold(). They are not prelude names, so code imports them. - annot.inline.recognizedThe compiler recognizes
std.annotation.inlineandstd.annotation.coldby their qualified names, through an alias or a module path. Their lines are compiler decorators and attach no value. - annot.inline.targetsAn
@inlineor@coldline must precede a module-level function or a method that has a body. Before a trait requirement without a default body, or before an@intrinsicdeclaration, it is an error. Error:invalid-inline-decorator. - annot.inline.argumentThe argument of
inlinemust be.Auto,.Always, or.Never, or left out, and it is read as written. Any other argument ofinline, and any argument ofcold, is an error. Error:invalid-inline-decorator. - annot.inline.bareA bare
@inlineis@inline(.Auto), byannot.decorator.bare-call-defaults. - annot.inline.exclusiveAn
@inlineline and an@coldline before one declaration are an error, reported on the later line. Error:invalid-inline-decorator. - annot.inline.duplicateTwo
@inlinelines, or two@coldlines, before one declaration are an error, reported on the later line. Error:duplicate-fact. - annot.inline.neverNo build copies the body of a function marked
@inline(.Never)or@coldinto a caller. - annot.inline.coldIn every build, each direct call of a
@coldfunction is a call through the function table. - 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
&dynor a function value is a call as before. - annot.inline.meaningNone of these lines changes a program's output, its panics, or its panic frames.
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 * 3An @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
@coldkeeps 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 byannot.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 => ""- annot.facts-of.declared
std.annotationdeclares the compiler intrinsicfacts_of. It is not a prelude name, so code imports it, as inuse std.annotation.facts_of. Its signature ispub fn facts_of[F](f: F) -> Facts. - annot.facts-of.targetA use of
facts_ofmust be a direct call whose one argument names a module-level function declaration. The name may go through a module qualifier or auseimport. The argument is not evaluated. - annot.facts-of.resultThe call returns a
Factsthat 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@coldlines. - 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_ofused as a value. Error:invalid-facts-of-target.
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-targetWhy. A function has no
Structure, so its values need a read of their own. Typing it asFactsreuses 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.
- annot.metadata.placesA field's or variant's metadata is written with
@valuelines on it and with member lines of a derivation block with no trait for its type. - annot.metadata.params-at-onlyParameter metadata, including a payload parameter's, is written only with
@valuelines on the parameter. - annot.metadata.reference-storageMember metadata and parameter metadata are stored as shared references in
List[*dyn Inspectable]. - 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.
- annot.metadata.eval-as-factEach metadata expression is evaluated as a fact expression is, and under the same
block_onban. - 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.withopts in, so a generator of the wrong type is an error at its decorator, asstd-testing.arbitrary.with.checkedstates 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:
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 |
- annot.target.declarations
std.annotationdeclaresTarget,Annotate, andannotate. They are not prelude names, so code imports them, as inuse std.annotation.annotate. - annot.target.limitA struct or enum
Fwhose type-level facts include anAnnotatevalue limits values of typeFto targets whose kind that value lists. - 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. - annot.target.unlimitedA type without an
Annotatefact is not limited: its values may be attached to any target, as@"note"may. - annot.target.recognizedThe compiler recognizes
std.annotation.Annotateby its qualified name. A type of another package namedAnnotatelimits nothing. - annot.target.bootstrap
Annotateitself carries@annotate(.Data, .Enum), so anAnnotatevalue may be attached only to a struct or an enum. - 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:
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: TNote. This heading keeps its earlier name so that links to it stay valid. The
MemberTypedmarker and@member_typedwere 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 |
- annot.typed-fact.declareA struct or enum whose
@annotatedecorator writes a type argument, as in@annotate::[T](.Field), is a typed fact type. The compiler recognizesstd.annotation.annotateby its qualified name and reads that argument. - 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], orfn(T) -> R. It describes the type of each target that the fact type's values attach to. - annot.typed-fact.pattern.scopeNames in the pattern see the fact type's type parameters, as the fact type's own fields do.
- annot.typed-fact.pattern.trivialA pattern that is one of those parameters alone, as
Tin@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. - annot.typed-fact.pattern.concreteA pattern that mentions none of the fact type's parameters, as
i32in@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. - annot.typed-fact.untyped.reference-valuesA fact type whose
annotatetype argument is the default*dyn Any, as in@annotate(.Field), is an untyped fact type. Its values stay unchecked, as Member Metadata states. - annot.typed-fact.untyped.no-annotateA fact type without an
Annotatefact is untyped too. - 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. - annot.typed-fact.declared-typeA target's type is the type written on the field, so for
hits: *mut Counterit is*mut Counter. A function's type is its signature as a function type, with its!and requirement row, as infn(i32) -> string. - 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. - annot.typed-fact.monomorphic.boundThere, a bound such as
IntegerorInteger & Displaystands for one fixed type that satisfies it, the same at each mention. It is not adyntype, so any bound is valid there. - 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). HerePis the fact type's type-parameter list with its bounds, andQis the pattern. - annot.typed-fact.infer.callThat call follows the ordinary rules for a call that infers its type arguments, by
fn.generic.call-listandtypes.generic.infer. A function target passes as a function value does, byfn.type.named-valueandfn.type.declared-variance. - annot.typed-fact.infer.suspendingSo a suspending function does not match a pattern written with
fn, such asfn(T) -> R. It could not be passed to a parameter of that type either. Itsfn!type is aSuspendFn, and the pattern is anFn, byfn.type.ctor.sugar. - 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, byreq.row.subsume.missing. - annot.typed-fact.infer.mismatchA target whose type does not fit the pattern is an error, as an
i32field or a two-parameter function is underfn(T) -> R. Error:type-mismatch. - annot.typed-fact.check.boundsAn inferred argument that breaks the bound of its parameter, as
stringbreaksF < IntegerinPositive[F < Integer], is an error, bytrait.bound.unsatisfied. Error:unsatisfied-trait-bound. - annot.typed-fact.checkA decorator or member-line element
vwhose type constructor is a typed fact typeDthen checks likelet f: D[A] = v.Aholds, in the order ofD's type parameters, the inferred arguments and a_for each parameter the pattern does not mention. Error:type-mismatch. - annot.typed-fact.check.other-paramsEach type parameter of
Dthat the pattern does not mention is inferred fromvas a_slot is, byfn.generic.placeholder.solve,.default, and.unsolved. Error:cannot-infer-type. - annot.typed-fact.check.inferredType arguments in
vare inferred from that expected type, byfn.type.generic.argument.sources, as in any such binding. - annot.typed-fact.check.reportedEvery error of the inference and the check is reported on the decorator or member line.
- annot.typed-fact.readA template reads such a fact typed, through the field's handle, as
annot.handle.fact.patternstates.
Note. In
@annotate(.Field), nothing solvesT, since neitherkindsnorAnnotatementions it. SoTtakes its defaultAny, by the ordinary ruletypes.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:
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: i64A function target checks against its signature's function type. In this
example, @handler on count checks like
let f: Handler[fn(i64) -> i64] = handler():
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:
idThe 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():
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 = ""- annot.derive.definitionTyped derivation implements a trait for a struct or enum from the type's members, through the trait's template.
- annot.derive.suppliedThe compiler supplies only
Structure, the handle constants, and the generatedwalk,describe, andbuildbodies. - 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.
- annot.derive.opt-in
@derive(...)is the only form that creates a derived implementation from a declaration. - annot.derive.acceptedIt accepts any trait that has a template, the comparison traits of Derived Implementations included.
- annot.derive.meansFor a trait
Xwith a template,@derive(X)before a struct or enumTmeans exactly the derivation blockimpl X for T by Structurewith an empty body. A newtype derives through its base type instead, as Derived Newtypes defines. - annot.derive.no-use
@derive(X)needs nouseofStructure. The import is needed only where code writesby Structure, asannot.block.structure-usestates. - annot.derive.otherAny other trait in a
@derivelist is an error, reported on the@deriveline. Error:underivable-trait. - annot.derive.error-trait
Errorhas no template, so@derive(Error)is an error. Error:underivable-trait. - annot.derive.facts-onlyEvery other decorator only attaches information: a configuration decorator such as
@style(prefix="user_")attaches a fact and creates no implementation. - annot.derive.overlapListing a trait in
@deriveand also writing a derivation block for it on the same type is an error, reported on the block. Error:overlapping-impl. - annot.derive.unknownA name in a
@derivelist that resolves to nothing is an error, reported on the@deriveline. Error:unknown-trait.
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
@errorintrinsic, 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]- annot.structure.declThe standard module
std.structuredeclaresStructure,Facts,Member,VariantInfo,Field,FieldRecord,Variant,VariantRecord,Members,MembersRecord,Key,KeyRecord,Walker,Describer, andSource.Facts,Member,VariantInfoand the four record types are struct values: a copy is independent of the value it was copied from.Field,Variant,Members, andKeyare read-only counted references to aFieldRecord,VariantRecord,MembersRecord, andKeyRecordrespectively. - annot.structure.bodiesThe compiler supplies every body written
passin the declarations above, and theFactstype. - annot.structure.reference-facts
Factsholds shared references to the facts attached to one type, member, variant, or module-level function, in source order. - annot.structure.find-copy
facts.find::[F]()copies the first fact of concrete typeFthroughdowncast_ref, or returns.None. - annot.structure.find-exact
findcompares exact concrete runtime identities, without conversions. - annot.structure.find-keyEach type parameter that
Fmentions must be bounded byInspectable, whose evidence supplies its runtime identity to the lookup. Without that bound, the call is an error. Error:unsatisfied-trait-bound. - annot.structure.members.at-option
members.at(position: usize)returns.Some(key)for the member at that position. - annot.structure.members.find-option
members.find(matches)returns.Some(key)for the first member thatmatchesaccepts. - annot.structure.members.lookup-noneEither lookup returns
.Nonewhen no member fits. - annot.structure.members.key-entryA
Key[S]represents a found member and carries itsMemberinformation. The keys and the member directory of one variant live as long as the program. - 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. - annot.structure.self-ref-enum
std.structurealso declares the enumSelfRef, whose three values Self References defines. - annot.structure.self-ref-fieldThe compiler computes the
self_reffield of everyMemberandVariantInfovalue it supplies.
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 => fallbackNote. A concrete
F, as infacts_of(list_users).find::[Route](), mentions no type parameter and needs no bound. The key rule holds for everyFacts, including one thatfacts_ofreturns.
Why. A shared generic body cannot find a fact of type
Mwithout knowingMat run time. TheInspectablebound already supplies that identity todowncast_ref, so the lookup reuses it.
Note. Standard walkers, describers, and sources, such as the ones a
std.jsonwould 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.
- annot.structure.sealed
Structureis a sealed trait: animpl Structure for Toutside the standard library is an error. Error:sealed-trait-implementation. - annot.structure.generatedThe compiler generates
Structurefor 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. - annot.structure.per-derivationEach derivation sees its own
Structurefor the target, which reflects that derivation's facts and omitted members. - annot.structure.named-positions
Structuremay be named, as a bound or in a call such asT::facts(), only inside a template. Outside one, it may appear only in theusedeclaration that imports it and afterbyin a derivation block's header. - annot.structure.named-positions.errorAny other use of
Structure, such as the bound infn fields[X < Structure], is an error. Error:structure-outside-template. - annot.structure.receivers
walktakes the value asself.facts,describe, andbuildare receiverless. - annot.structure.protocol-ref
walk,describe, andbuildtake the walker, describer, or source as a&mutreference, and every method ofWalker,Describer, andSourcetakes&mut self. So a struct walker changes in place, and its caller reads the walker's fields after the traversal. - annot.structure.private
walk,describe, andbuildinclude private members. Opting a type in lets the template's library read every member. - annot.structure.pure
walk,describe, andbuild, and every method ofWalker,Describer, andSource, have the empty requirement row and are not suspending. - annot.structure.pure.implAn implementation method of
Walker,Describer, orSourcethat declares a requirement or suspends is an error. Error:trait-method-signature. - annot.structure.name
T::name()returns the target's declared name, with no module path and no type arguments. Likefacts,nameis receiverless. - annot.structure.name.constant
T::name()is a compile-time constant. Like every use ofStructure, it may be called only inside a template. - annot.structure.name.newtypeA newtype has its own name: its
name()is the newtype's declared name, not its base type's. - annot.structure.name.aliasA transparent alias has no
Structureof its own. A derivation through it sees its base type's, soname()is the base type's declared name.
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
passFor struct Box[T] declared in the module shapes, T::name() is
"Box", not "shapes.Box" or "Box[i32]".
Note. A newtype gets no
Structuretoday (trait.derive.newtype.templated), so no template reads a newtype'sname(). A derivation through the base type sees the base type's name.annot.structure.name.newtypeapplies once newtypes gain aStructure.
Why. Traversal stays pure, as serde's is: input and output happen before
buildor afterwalk, 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 throughInspectable. 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- annot.template.form
impl[T] Trait for T by Structure:declares the template ofTrait. - 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. - annot.template.moduleA template must be declared in the module that declares its trait. One declared elsewhere is an error. Error:
misplaced-derivation. - annot.template.uniqueA trait has at most one template. A second one is an error. Error:
overlapping-impl. - annot.template.structureInside a template,
TimplementsStructure. Its bodies may callStructure::walk(self, w),T::describe(d),T::build(s), andT::facts(). - annot.template.qualified-selfInside a template, a call qualified by
Structureor by the derived trait,Structure::f(args)orTrait::f(args), has the template'sTas itsSelf. This replaces theSelfthattrait.assoc-call.traitinfers. - annot.template.qualified-self.receiverlessSo a receiverless qualified call is valid in a template.
Structure::name()isT's generatedname,Structure::facts()isT::facts(), andEncode::name()inEncode's template isEncode'snameforT. - annot.template.bodyA template must have a body in which at least one method calls
walk,describe, orbuild. - annot.template.body.errorA template without such a body, such as a bodiless marker template, is an error. Error:
marker-template. - annot.template.instanceEach derivation instantiates the template once, as one ordinary implementation of the trait for the target, in the target's module.
- 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.
- annot.template.checkedThe instantiated implementation is type-checked at its opt-in, where the member types and the walker's bounds are both known.
use std.structure.Structure
trait Tagged:
fn tag(self) -> string:
"tagged"
impl[T] Tagged for T by Structure # error: marker-templateWhy. 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
stddeclares, the one forDefaultreads a@defaultfact to choose an enum's default variant (Derived Default).
Why. A template has exactly one target, so
Structureand the derived trait each have oneSelfinside it. A receiverless call such asStructure::name()orEncode::name()has no argument to infer thatSelffrom.
Note. The generated
walk,describe, andbuildkeep their names. This holds even when the derived trait or another trait ofThas a method of the same name. The qualified callStructure::walk(self, w)always calls the generatedwalk.
Note. The receiverless
factsandnameclash the same way when the derived trait declares its own receiverless member of that name. Inside the template, qualifying the call resolves it. WhenEncodedeclares its ownname,Structure::name()is the generated one, andEncode::name()isEncode's. Both haveTas theirSelf, byannot.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- annot.template.tuple.form
impl[T < Tuple] Trait for T by Structure:declares the tuple template ofTrait, whereTupleisstd.function.Tuple. - annot.template.tuple.rulesThe template rules for its module, its uniqueness, its body, its
Structurecalls, and its qualified calls apply to a tuple template as well. - annot.template.tuple.separateA trait may have both a template and a tuple template.
@deriveuses only the template, so a trait with only a tuple template is not derivable. - annot.template.tuple.implementsA tuple type implements
Traitthrough its tuple template exactly when each of its members meets the obligation ofannot.walker.obligation. - 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. - 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. - annot.template.tuple.instanceEach tuple type that a program uses with
Traitinstantiates the tuple template once, as an ordinary implementation.
Why. A tuple has no declaration to write
@deriveon, so the trait opts every tuple in. A separate form keeps@derivemeaning one thing. So a trait that suits only tuples, such as a tupleDisplay, 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- annot.block.form
impl Trait for X by Structure:is a derivation block: it applies the template ofTraittoX. - annot.block.template
Traitmust have a template. A block for a trait without one is an error, and so is a block forEq,PartialOrd,Ord, orHash, whose templates only@deriveinstantiates. Error:underivable-trait. - annot.block.moduleA derivation block must be declared in the module that declares
X. One declared elsewhere is an error. Error:misplaced-derivation. - annot.block.structure-useIn an implementation header,
Structureafterbymust namestd.structure.Structure, imported withuse std.structure.Structurelike any other name. Without that import,Structureis unknown. Error:unknown-trait. - annot.block.not-delegation
by Structureis never a delegation, astrait.by.structurealso states. - 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.
- annot.block.linesA block may carry member lines, which edit the
Structurethat this derivation sees. - annot.block.localA derivation block's member lines are local to it. Another derivation for the same type sees only the declaration facts.
- 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. - 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. - annot.block.newtype.errorA derivation block whose target is a newtype is an error, reported on the block. Error:
misplaced-derivation.
use std.structure.Structure
type Meters(i64)
impl Show for Meters by Structure # error: misplaced-derivationA module that writes by Structure without importing Structure gets the
ordinary unknown-trait error:
struct Point:
x: i64
impl Show for Point by Structure # error: unknown-traitNote. 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 |
- 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. - 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. - annot.line.right-typedThe right side must be an expression of a list type, or
passafter a member name and=. A named list, as inname = shared_list, needs no spread. - annot.line.right.errorAny other line is an error. That includes
f += pass,Self = pass, andpassfor a whole variant, such asBusy = pass. Error:invalid-member-line. - 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. - annot.line.reference-elementsA member line builds
List[*dyn Inspectable], checking typed fact elements byannot.typed-fact.check. - 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. - annot.line.unchangedA member without a line keeps its declaration facts.
- 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.
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-lineWhy. A whole variant cannot be omitted, because
buildcould never produce it. A payload member has no line, because a line names what a derivation walks as a unit.
#Omitted Members
- annot.omit.skipA member omitted with
= passis not walked or described, andbuildnever offers it to the source. - annot.omit.default
buildfills an omitted member by evaluating its declared default. - annot.omit.no-defaultOmitting a member that declares no default is an error, reported on the member line. Error:
omitted-member-without-default. - annot.omit.exemptAn omitted member's type need not satisfy any walker's, describer's, or source's bound.
- annot.omit.only-code
= passis the only member line that changes generated code. Every other line changes only facts.
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-defaultAn embedded field has no default syntax
(data.default.embedded),
so omitting one, as Audit = pass above, is always this error.
#Line Drift
- 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. - annot.line.drift.lint.deriveA block beside another same-package traitโs
@deriveproduces the same lint finding when it contains any member line. Warning:derivation-line-drift. - 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 warningWhy. Most blocks of one library should agree, so a difference is worth a second look. Some differences are deliberate, as serde's
skip_serializingshows.
#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.
- annot.no-trait.form
impl X by Structure:, written without a trait, is a derivation block with no trait. It writes metadata ofXand implements nothing. - annot.no-trait.not-inherentA block with no trait is not an inherent implementation: it declares no members of
X. - 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. - annot.no-trait.no-omitIt writes only metadata, so an omit line
f = passin it is an error. Error:invalid-member-line. - 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. - annot.no-trait.selfIts
Selflines apply the same way to the type-level facts that decorators beforeXattach. - annot.no-trait.declaration-factsThe result is the declaration facts of
Xand its members: every derivation ofXsees it. - annot.no-trait.then-blocksA derivation block's member lines then edit those declaration facts, for that block only.
- 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. - 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. - annot.no-trait.target
Xmust be a struct or an enum. Any other target, including a newtype, is an error, reported on the block. Error:misplaced-derivation. - annot.no-trait.byThe name after
byin a header without a trait must beStructure, astrait.by.no-traitstates. - annot.no-trait.genericThe header must declare only the own type parameters of
X's declaration, in order and without bounds, and applyXto them, asimpl[T] Box[T] by Structure:. - annot.no-trait.generic.renameThe header may rename the parameters, as
impl[U] Box[U] by Structure:forstruct Box[T]. Only their count, their order, and the absence of bounds are checked. - annot.no-trait.generic.errorAny other header, such as
impl Box[i32] by Structure:orimpl[T < Hash] Box[T] by Structure:, is an error, reported on the block. Error:misplaced-derivation. - 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.
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-implWhy. 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- annot.fact.descriptiveFacts are descriptive: a fact changes no generated call. Only a template's own code reads it.
- annot.fact.type-level-decoratorA decorator before a struct or enum declaration attaches a type-level fact, as
@style(prefix="user_")does. - 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.
- annot.fact.payloadA payload member's declaration facts are the values of the decorators before its payload parameter.
- annot.fact.sharedDeclaration facts are seen by every derivation of the type. A derivation block's member lines edit them for that block only.
- 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. - 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.
- annot.fact.requirement-freeA fact expression must be requirement-free, as defined for default values.
- annot.fact.valueA fact may hold any value that a global may hold.
- 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. - 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. - annot.fact.readA template reads the type-level facts through
T::facts(), and a member's or variant's facts through its handle'sinfo.facts. - annot.fact.defaultA template falls back to its own default when a fact is absent. An absent or foreign fact is never an error.
- 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. - 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. - annot.fact.unused-block-decorator.fixThe warning offers a fix-it that moves the value into the block as a
Self += [...]member line. - 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.
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- annot.fact.block-on.directA call of
std.task.block_onorprintlnwritten directly in a fact or metadata expression is an error. Error:suspension-forbidden-context. - annot.fact.block-on.indirectSuch a call reached through another call while the expression is evaluated panics, as
req.drive.block-on.indirectsays. Panic:suspension-forbidden-context.
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: i64Why. 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_onorprintlncall happens while the program evaluates the fact. So it is reported asfact-evaluation-failed, byannot.fact.eval.panic-runtime, namingsuspension-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
- annot.walk.matchGenerated
walkmatches the value once and callsw.variant(v)for the value's variant. - annot.walk.membersIt then calls
w.member(h, value)for each member of that variant, in declaration order. - annot.walk.valueEach member value is read from
self, so it has the member's declared type. - annot.walk.errorThe first
.Errthatvariantormemberreturns ends the walk, andwalkreturns it. Otherwisewalkreturns.Ok(()). - annot.walk.nestedA member is passed as one value. Its own members are reached only through its own type's implementations.
- annot.walk.restFor a rest member, generated
walkcallsw.rest(h, items)in place ofw.member, with the rest element's list asitems. - annot.walk.rest.default
Walker's defaultrestcallsself.member(h, items). So a walker that does not implementrestsees the rest member as oneList[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
- annot.describe.orderGenerated
describecallsd.variant(v)for every variant in declaration order, each followed byd.member(h)for that variant's members in declaration order. - annot.describe.no-value
describeholds no value. - annot.describe.errorThe first
.Errends the traversal and is returned, as forwalk.
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
- annot.build.variantGenerated
buildfirst callss.variant(choices)once, with every variant in declaration order. A struct offers its one variant. - annot.build.next-optionIt then calls
s.next(members)with the chosen variant's members, repeatedly, until the source returns.Ok(.None). - annot.build.memberFor each member key, it calls
s.member(h, previous), wherepreviousis the value already read for that member, or.None. - annot.build.missing-after-noneAfter
.Ok(.None), it callss.missing(h)once for each member that the source never named. - annot.build.constructIt then constructs the chosen variant from one local per member, and returns it.
- annot.build.errorsThe first
.Errfrom a source method ends the build and is returned. Generated code never creates an error value of its own. - 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. - annot.build.shared
buildnever 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
- annot.member.infoEach member has a
Membervalue: its name, its zero-based position within its variant, its facts, its doc comment, and theembeddedandpositionalflags. - annot.member.embeddedAn embedded field is one member, named by its embedded type's final name, with
embeddedtrue. - annot.member.positionalAn unnamed payload parameter is a member named
_0,_1, and so on, by position, withpositionaltrue. - annot.variant.documented-infoEach variant has a
VariantInfovalue: its name, zero-based index, facts, variant doc,type_doc,of_data, andshared. - annot.variant.dataA struct's one variant has
of_datatrue, and its name and doc comment are the type's. - annot.variant.data-factsThat variant's
factsis empty: a template reads the type-level facts once, throughT::facts(). - annot.variant.shared-pairs
sharedholds the variant's shared constructor data as(name, value)pairs. An unnamed shared parameter is named_0,_1, and so on. - annot.variant.shared.no-handleShared constructor data is never a member: it is never passed as a handle.
- annot.member.position-usize
Member.positionhas typeusize. - annot.variant.index-usize
VariantInfo.indexhas typeusize. - annot.member.documentation
Member.docis the attached declaration documentation normalized bylex.doc.attach, or.Nonewhen absent. - annot.variant.documentation
VariantInfo.docis the variant's attached declaration documentation, or.Nonewhen absent. - annot.variant.type-documentationEvery variant of a declared type carries that type's attached documentation in
type_doc, or.Nonewhen absent. - annot.variant.type-documentation-sameEvery variant of the same type carries the same
type_doc. - annot.variant.type-documentation-defaultA manually constructed
VariantInfodefaultstype_docto.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
embeddedflag.
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] |
- annot.tuple.structureThe compiler generates
Structurefor a tuple type only while it instantiates a tuple template for that type. - annot.tuple.variantA tuple's one variant has index
0,of_datatrue, the name"", no facts, no doc comment, and no shared data. - annot.tuple.membersIts members are its elements in order, named
_0,_1, and so on, withpositionaltrue,embeddedfalse, no facts, and no doc comment. - annot.tuple.nameFor a tuple type,
T::name()is""andT::facts()is empty. - annot.tuple.restA tuple with a rest element
List[T]...has one rest member for it: its last member, of typeList[T]. - 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. - annot.tuple.no-defaultNo member of a tuple has a default.
- annot.tuple.buildGenerated
buildconstructs the tuple from one value per member. The rest member's list becomes the rest element's items. - annot.tuple.no-type-documentationA tuple's variant has
type_doc.None.
Why. One
Listmember keeps a rest tuple'sStructurethe same size for every value, as its type is. A walker that needs the items one by one, as text does, implementsrest.
#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.
- annot.self-ref.enclosingThe enclosing type of a member or a variant is the struct or enum that declares it.
- 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. - 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.
- 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.
- annot.self-ref.refers.referenceA reference type,
*T,*mut T,&T, or&mut T, refers to the enclosing type whenTdoes. - annot.self-ref.needs.selfA type needs the enclosing type when it is the enclosing type.
- 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.
- annot.self-ref.needs.result
Result[T, E]needs it whenTdoes. - annot.self-ref.needs.referenceA reference type,
*T,*mut T,&T, or&mut T, needs it whenTdoes, since a reference always refers to a value. - 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.
- annot.self-ref.needs.stopA
List,Map, or optional type never needs the enclosing type, whatever its type arguments. - annot.self-ref.needs.onlyNo other type needs the enclosing type.
- annot.self-ref.memberA member's
self_refis.Requiredwhen its type needs the enclosing type,.Optionalwhen its type refers to it without needing it, and.Absentotherwise. - annot.self-ref.variantA variant's
self_refis the strongest of its members' values, where.Requiredis stronger than.Optionaland.Optionalthan.Absent. - annot.self-ref.variant.emptyA variant with no members has
self_ref.Absent. - annot.self-ref.variant.omittedA variant's
self_refalso 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 Nodewithparent: &Node?and a block lineparent = pass, the one variant ofNodestill hasself_ref.Optional, although its derivation walks onlylabel.
Note. Templates other than
Arbitrarymay readself_reftoo, as library design. When anyself_refis not.Absent, a codec may add a nesting-depth limit, and a schema generator may emit a named definition with a$ref.Debugmay 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.
- annot.handle.constantsEach member has one constant handle of type
Field[S, F], and each variant one of typeVariant[S]. A handle refers to a record that lives as long as the program. - annot.handle.member-type
walk,describe, andbuildpass handles whoseFis the member's declared type: forhits: *mut Counter, it is*mut Counter. - annot.handle.field-variance
Fieldis invariant inSandF, andVariant,Members, andKeyare invariant inS, as a counted reference is in its referent (ref.type.kind-distinct). - annot.handle.get
h.get(x)accepts anyS. Its type is the type that the field accessx.memberwould have, for a member declaredF. - annot.handle.get.variant
h.get(x)on a payload member, whenxholds another variant, is a checked runtime panic with categorystructure-variant-mismatch. - annot.handle.holds
v.holds(x)is true exactly whenxholds the variantv. - annot.handle.default
h.has_default()is true when the member declares a default.h.default()evaluates that default, or returns.Nonewhen there is none. - annot.handle.escapeHandles are ordinary values and may escape the traversal that passed them.
- annot.handle.fact.typed
h.fact::[D]()reads the member's fact of the typed fact typeD, or.None. - annot.handle.fact.patternIt matches
D's pattern against the handle'sFexactly as the attach-time check does, byannot.typed-fact.infer. Its type isD[A]?, whereAholds the inferred arguments. - annot.handle.fact.pattern.exampleSo under
@annotate::[List[T]](.Field) struct MaxLen[T],h.fact::[MaxLen]()on the handle oftags: List[string]has typeMaxLen[string]?. - annot.handle.fact.pattern.mismatchAn
Fthat does not fitD's pattern is an error, as it is at attach time. Error:type-mismatch. - annot.handle.fact.pattern.written
Dmay be written with its type arguments, as inh.fact::[Fallback[F]](). They must be the arguments that the match infers. Error:type-mismatch. - annot.handle.fact.exactIt finds only a fact whose type is exactly
D[A]. - annot.handle.fact.keyThe type parameters that
D[A]mentions followannot.structure.find-key, except the handle's ownF, which needs no runtime identity. Error:unsatisfied-trait-bound. - annot.handle.shared-targetTraversals using the same
Structureat 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)invariantbefore anyh.get(other). The standard-libraryClonetrait, listed in STDLIB, declaresclone(self), whose source reads each member from the value, asCopySourcedoes.
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 toF, so the lookup matches that part without a runtime key. A walker'smember[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(())- annot.walker.strengthen-memberAn implementation of
Walker,Describer, orSourcemay strengthen the bound onmember[F], asF < Encodeabove. - annot.walker.strengthen-restAn implementation of
Walkermay implementrestand strengthen the bound on itsT, asrest[T < Display]does. - annot.walker.missing-fixedA
Sourceimplementation must not strengthen the bound onmissing[F]: it keeps the trait's unboundedF. - annot.walker.missing-fixed.errorA strengthened bound on
missing[F]is an error, reported at the implementation method. Error:trait-method-signature. - annot.walker.not-sealed
Walker,Describer, andSourceare not sealed. Any package may implement them. - 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.
- annot.walker.obligation.restFor a rest member, a walker that implements
restputs the obligation on the item typeT, byrest's bound. Otherwise the obligation is onList[T], bymember's bound. - 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. - annot.walker.generic-member-call
membermay 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. - annot.walker.generic-rest-call
restmay 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. - annot.walker.generic-missingCode outside generated code may call
missingthrough a generic source type. - annot.walker.concrete-callA call on a concrete walker, describer, or source type applies that type's own bounds.
@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
missingstays fixed, so a genericmissingcall 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.
- annot.impl-fact.fills-defaults.decl
std.structuredeclares the fact typeFillsDefaults, limited to implementations by@annotate(.Impl), andfills_defaults() -> FillsDefaults. The compiler recognizesstd.structure.fills_defaultsby its qualified name. - annot.impl-fact.fills-defaultsWhen a template's
buildcall passes a source whose implementation carriesfills_defaults(), generatedbuildfills each member that declares a default from that default. It never offers such a member to the source. - annot.impl-fact.fills-defaults.exemptSuch a member's type need not satisfy that source's bounds.
- annot.impl-fact.fills-defaults.scopeThe fact changes only that derivation. Every other derivation of the type still walks, describes, and builds the member.
- 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.
- annot.impl-fact.skips.decl
std.structuredeclares the fact typeSkips[F], limited to implementations by@annotate(.Impl), andskips[F]() -> Skips[F]. The compiler recognizesstd.structure.skipsby its qualified name and reads its type argument. - 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 typeFis omitted. It is omitted as= passomits it, byannot.omit.skipandannot.omit.default. - 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.duplicatestates. - annot.impl-fact.skips.exemptAn omitted member's type need not satisfy any bound of that derivation's walkers, describers, or sources.
- annot.impl-fact.skips.scopeThe omission changes only that derivation. Every other derivation of the type still walks, describes, and builds the member.
- 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
- annot.bound.paramsA derived implementation for a generic type gets
T < Traitfor each type parameterTthat appears in a walked, described, or built member. - annot.bound.declaredA derived implementation also keeps every bound the type declares on its parameters. So
@derive(Eq) struct Pair[T < Eq & Debug]derivesimpl[T < Eq & Debug] Eq for Pair[T]. - annot.bound.omittedAn omitted member contributes no bound.
- annot.bound.recursiveRecursion is checked coinductively: while checking the members of
Tree[T], its own derived implementation is assumed to hold. - annot.bound.moreWhen a member needs more than
T < Trait, as aMap[T, V]member needsT < 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 itNote. 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
Cliat a derive. Their typedShort[F < Cli]field fact also requires a CLI-readable field. The typedPositional[F < Cli]field fact has the same bound.
See also: trait.derive.bounds,
Derived Newtypes.
#Limits
- 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.
- annot.limit.interfacesA package interface carries each template's body, the walker, describer, and source bodies it names, and the functions its facts call.
- 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 oneNote. 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, asannot.walker.generic-member-callstates.
Note. Wire stability is documented, not checked. Reordering or renaming members or variants changes derived formats,
VariantInfo.index, and derivedOrd. 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- annot.serde.definitionA type opts into serialization when it implements
std.serde.Serialize, and into deserialization when it implementsstd.serde.Deserialize. - annot.serde.private-library-viewThe opt-in covers private members, as
annot.structure.privateallows. - annot.serde.derivable
std.serdedeclares the template of each trait. So@derive(Serialize),@derive(Deserialize), and a derivation block for either trait are valid. - annot.serde.membersEvery member that a derived
Serializewalks must implementSerialize, and every member that a derivedDeserializebuilds must implementDeserialize. - annot.serde.members.errorA member that does not is an error, reported at the opt-in and naming the member. Error:
member-not-derivable. - annot.serde.by-handA type may implement either trait by hand instead. A written implementation opts in too.
- annot.serde.no-dynNo format selects a type from the data: deserialization never constructs a polymorphic or
dynvalue from a payload. A payload that names its own type is data, not instructions: Java's gadget chains show where trusting it leads.
use std.serde.Serialize
struct Secret:
value: i32
@derive(Serialize) # error: member-not-derivable
struct Account:
id: i32
secret: SecretWhy. Rust's serde and Swift's
Codablework 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.Renamefield 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- annot.serde.moduleThe standard module
std.serdedeclaresSerialize,Deserialize,Serializer,Deserializer, andValueKind, as above.Facts,VariantInfo, andMemberare those ofstd.structure. - annot.serde.knownThe compiler knows
SerializeandDeserializeby name, for the boundary rulemodule.boundary.out. It knows no other part ofstd.serde. - annot.serde.not-sealedNone of the five items is sealed. Any package may implement the four traits.
Why.
Structuremay 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
SerializerandDeserializercall, 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
- annot.error.intrinsic
@erroris a compiler intrinsic, as@deriveis. It is not an ordinary decorator and attaches no value. - annot.error.name
@erroralways means the intrinsic. A binding namederrorin scope, such as an import, does not change it. - annot.error.typeAn error type is an enum with a bare
@errorline, or a struct with an@error("...")or@error(transparent)line. - annot.error.generatesFor an error type
E, the compiler generatesimpl Display for E,impl Error for E, and oneimpl From[P] for Efor each@frommember. - annot.error.ordinaryThe generated implementations are ordinary implementations, under the ordinary rules for coherence and overlap.
- annot.error.hand-writtenA hand-written implementation of
Display,Error, or a generatedFrom[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 |
- annot.error.form.otherAny other
@errorline is an error, and so is any other@fromor@sourceline inside an error type. A form on the wrong target isdecorator-target-kind; an invalid argument or marker argument isinvalid-error-marker, as the rules below specify. - 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. - annot.error.form.misplaced.examplesSo
@errorbefore a function or a newtype, and a bare@errorbefore a struct, are eachdecorator-target-kind. So are@error("...")before an enum, and@frombeside a second payload member. - annot.error.form.argumentAn
@errorline whose arguments are neither one message nortransparentis an error, reported on the line. Error:invalid-error-marker. - annot.error.form.argument.examplesSo
@error(opaque),@error(42), and@error("closed", "shut")are eachinvalid-error-marker. - annot.error.form.marker-argumentAn
@fromor@sourceline 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. - annot.error.markerInside an error type,
from,source, andtransparentin these forms are markers, not names. A binding with the same name does not change them. - annot.error.marker.no-valueA marker attaches no value, so it is neither member metadata nor a fact.
- annot.error.marker.outsideOutside an error type,
@fromand@sourcehave no special meaning: each is an ordinary decorator. - annot.error.no-useWriting
@error,@from, or@sourceneeds nouse std.error.Error, asannot.derive.no-useneeds none forStructure. Code that namesErroritself still imports it, astrait.error.importstates.
@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-markerWhy. Typed derivation generates one implementation per trait. An error type needs one
Fromper marked variant, each a different instantiation, andDisplay,Error, andFromall 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
- 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. - 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. - annot.error.message.unnamedUnnamed payload members are numbered from zero, in declaration order.
- annot.error.message.sharedA variant's message also sees the enum's named shared fields by name.
- annot.error.message.shared-unnamedUnnamed shared data is not in scope, so
$_0and$_1always name the variant's unnamed payload members. - annot.error.message.dataA struct's message sees the struct's fields by name.
- 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. - annot.error.message.displayEach interpolated value must implement
Display, asexpr.interp.no-displayrequires. Error:unsatisfied-trait-bound. - annot.error.message.no-selfA message is not inside a method, so
selfnames nothing there, and$selfin a message is an error. Error:unknown-name. - annot.error.message.evalThe generated
to_stringevaluates the value's message each time it is called, with the value's members bound. - annot.error.message.absentA variant without a message displays as its variant name, as
FsError.Busydisplays asBusy.
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.
$selfwould call the generatedto_stringfrom inside itself and never finish.
#Error Causes
- annot.error.source.reference-resultThe generated
Error.sourcereturns a shared reference to the cause member, or.Nonewhen it has none. - annot.error.cause.memberA variant's or struct's cause member is its
@fromor@sourcemember. - annot.error.cause.explicitA variant or struct without
@fromor@sourcehas no cause. There is no automatic cause and no field-name convention. - annot.error.cause.oneA variant or struct has at most one
@fromor@sourcemember. A second one is an error, reported on that member. Error:invalid-error-marker. - annot.error.cause.typeA cause member's type must implement
Error. Error:unsatisfied-trait-bound. - annot.error.cause.optionalA
@sourcemember may instead have the typeE?, whereEimplementsError. A.Nonemember gives no cause.
@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
- annot.error.from.generate
@fromon a variant's only payload member of typePgeneratesimpl From[P] for E, whosefrombuilds that variant. - annot.error.from.data
@fromon an error struct's only field generates the same implementation, whosefrombuilds the struct value. - annot.error.from.unmarkedA member without
@fromgenerates noFrom, soInvalid(reason: string)never yieldsFrom[string]. - annot.error.from.same-typeTwo
@frommembers of one typePgenerate overlapping implementations ofFrom[P]. The later one is an error, reported on its member. Error:overlapping-impl. - annot.error.from.type-parameterA
@frommember whose type is a bare type parameter is an error. Error:invalid-error-marker.
@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-markerWhy.
impl[E] From[E] for AppError[E]would overlap every otherFromofAppError, as a generic#[from]field does in Rust. Code converts such an error explicitly, as inmap_err(AppError.Inner).
#Transparent Errors
- annot.error.transparent.displayA transparent variant or struct displays as its one member displays.
- annot.error.transparent.sourceIts
sourcereturns that member's ownsource, so a chain does not repeat the inner message. - 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: RepoErrorPublicError hides RepoError behind a stable public type, and ?
converts a RepoError into it.
Note. Because a transparent value's
sourceskips the inner error,find::[Inner]()does not see that inner error itself, as in Rust.
#Generated Error Bounds
- annot.error.bound.displayFor a generic error type, the generated
DisplaygetsP < Displayfor some type parameters. That is eachPthat is the type of an interpolated member or a transparent member. - annot.error.bound.errorThe generated
ErrorgetsP < Errorfor each type parameterPthat is the type of a@fromor@sourcemember. - annot.error.bound.display-carriedThe generated
Displaygives no bound to a type parameter that is only carried. - annot.error.bound.transparent-sourceThe generated
ErrorgetsP < Errorfor each type parameterPthat is the type of a transparent member, so itssourcecan forward. - annot.error.bound.carriedThe generated
ErrorgetsP < Inspectablefor each type parameterPthat is only carried. Such aPis the type of no interpolated, transparent,@from, or@sourcemember. - annot.error.bound.interpolatedThe generated
ErrorgetsP < Display & Inspectablefor each type parameterPthat only interpolated members have as their type. That is, an interpolated member has the typeP, and no transparent,@from, or@sourcemember 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 Errorneeds an inspectable target, astrait.error.not-inspectablestates, and an unbounded type parameter is not inspectable.Displayneeds no such bound, so it keeps none.
See also: Error Trait, Conversion Trait, Propagation.