@ Before a Declaration, Marked by @meta::Attribute, and Read as Members; a Struct Field May Have a DefaultStatus: Accepted Date: October 7, 2026 (#496) Answers: #496, how a program says more about a field, a type or a function than its type and documentation - a name on the wire, an example, a constraint, a header a parameter comes from Related: ADR-330 (a type as an argument, members of a type); ADR-329 (what an API description is built from); ADR-318 (a default is a build-time value); ADR-304 (reflection, no macros); ADR-251 (the ledger); Part I 4.1-4.2, 5.1; Part II 10.3
Where does a program write what a reader of its types needs beyond the types - and how is it checked, defined, read, and changed without breaking those who use it?
A field created_at that JSON calls createdAt, an example value for an API
description, a parameter read from a header: nothing in the language says
these. /// is prose the compiler reads nothing from (Part I), and a reflected
field answers its name, value, type and documentation only
(ADR-330).
| Mechanism | Defined by | Where it may stand | Repeated | |
|---|---|---|---|---|
| Java | annotations, @JsonProperty("x") |
@interface |
@Target |
@Repeatable |
| Kotlin | annotations | annotation class |
@Target(AnnotationTarget.FIELD) |
@Repeatable |
| C# | attributes, [JsonPropertyName("x")] |
a class deriving Attribute |
[AttributeUsage(…)] |
AllowMultiple |
| PHP 8 | attributes, #[Name('x')] |
a class marked #[Attribute(TARGET_PROPERTY)] |
in that same mark | IS_REPEATABLE |
| Rust | attributes read by derive macros | the macro | the macro decides | the macro decides |
| Go | struct tags, json:"x" |
nobody; text | fields only | - |
| Python | Field(alias="x") as a default |
ordinary values | fields | - |
From memory, not checked against a version. What transfers: PHP’s one mark that says both this is an attribute and where; a constructor-like call with positional and named arguments; repetition allowed by the definition.
| Option | Why it lost |
|---|---|
Directives in /// (Go’s tags, swag) |
text nobody checks; Part I keeps documentation prose |
A trait impl naming fields as text (Zig’s declarations) |
a misspelled field name is not checked, and the fact stands away from the field |
Any pub struct is an attribute |
one placed where nobody reads it, or before the wrong kind of declaration, is a mistake nobody sees |
A trait meta::Annotation with a constant list of places |
an impl block for one line |
A declaration of its own, annotation Name on field { … } |
a new kind of item that is almost a struct |
The mark in the prelude, @AnnotationTarget(field) |
the prelude holds what a program cannot do without; meta can grow without breaking a name |
Annotation, Target in the name |
written and read often; Attribute is shorter and says enough |
| Inferring an enum variant from the parameter type everywhere | a rule for the whole language, under which a name may be a variant or a binding |
The struct literal’s braces at the use, @json::Name { value: "x" } |
long for the common one-field case |
| Defaults only for attribute structs | every pub struct has the same problem: a field added breaks every literal elsewhere |
@ before a declaration.struct User {
/// When the user was created.
@json::Name("createdAt")
@openapi::Example("2024-01-01T00:00:00Z")
created_at: time::Instant,
}
It stands after a declaration’s /// lines and before the declaration. Its
arguments are build-time values, as a comptime holds (Part II 10.2). Nothing
of it reaches the running program but what a build-time reading makes of it.
@meta::Attribute(…) marks it, naming where it may stand.@meta::Attribute(field)
pub struct Name { pub value: String }
The places are field, struct, enum, variant, fn, parameter,
trait and impl. @ with a struct not so marked, or before a declaration
its mark does not name, is NK1235. meta::Attribute is the one attribute
std declares for this; meta stays a module of its own, so it can grow
without taking a name from a program.
field, not meta::Target::Field: the compiler reads the variant off the
parameter’s enum. Only there; everywhere else a variant is written whole.
;.@meta::Attribute(field)
pub struct Name {
pub value: String,
pub case: Case = Case::Camel,
}
@json::Name("createdAt")
@json::Name("created_at"; case: snake)
Positional arguments fill the fields without a default, in declaration order; a field with a default is an option. A field added with a default breaks no use; a field added without one, or a field renamed or moved among those without, does. The form is an attribute’s only; a struct literal keeps its braces and names.
pub struct Page {
pub size: i64 = 50,
pub cursor: String?,
}
let p = Page { cursor: null } // size is 50
A default is a build-time value (ADR-318), evaluated once where
the struct is declared. A literal may leave such a field out; one that leaves
out a field without a default is NK1234. A reflected field answers
.default, its default as a build-time T? - null where it has none.
field.attribute(X), T::attribute(X).let wire = field.attribute(json::Name)?.value ?? field.name
X is a type (ADR-330 D1), so a misspelled one is NK1135, and
the result is an X? with typed fields. A reflected variant answers
.attribute(X) as well. An attribute on a function, a parameter, a trait or an
impl is checked and carried; reading it waits for reflection over those,
which is not decided.
repeatable: true.@meta::Attribute(field; repeatable: true) allows it several times, and
field.attributes(X) reads them as a list in written order. A second one
without it is NK1236.
A type’s attributes, its fields’ and its variants’, and every field’s default,
are written to the ledger as build-time values cross (ADR-251),
so a package that reads them sees those of a dependency’s types. /// stays
out of it.
std but the mark.pub struct can grow a field with a default without breaking anyone.@ is a new token and an attribute’s positional form a
second way to build a struct value; a declaration can carry a long list
before it; the ledger grows by the attributes and defaults it carries.impls, which
waits for reflection over them.std, json, http and openapi declare, each the
package’s own.