comptime Crosses In; a std Type the Compiler Cannot See Into Crosses Through the Parts Its Ledger Entry NamesStatus: Accepted
Date: October 4, 2026
Answers: #374
(an option whose type is time::Duration cannot have a default), the
owner’s choice of its first direction
Related:
Part I 5.1 (options and their defaults);
ADR-287 (what a build-time body may do; D10’s one evaluator, D13-D14’s rule, D18’s interpretation);
ADR-311 (what a build-time value may hand over, and in which form);
ADR-150 D1-D2 (time::Duration is Rust’s own type; a span is written 5.seconds() and has no second spelling);
ADR-251 D4 (the ledger’s signature is spelled as Nikaia spells a declaration);
ADR-296 D17 (the emitter knows no types);
ADR-123 (crosses, a column for a type whose parts the compiler cannot walk);
ADR-294 D18 (std’s Nikaia half)
What may an option’s default be, when is it computed, and how does it reach a call that leaves the option out?
The language below has no defaults, so a left-out option is written into the
call by the compiler, from the callee’s ledger entry. A literal makes that
trivial: the ledger holds its text, and the text is the value in both
languages. A span of time is not a literal — 30.seconds() is a call — so an
option whose type says its unit cannot have a default, and the unit moves into
the parameter’s name over a bare number.
The question is general. Duration is the first type that needs it; any value
that is not a literal raises the same three things: when it is evaluated,
what the ledger records, and how the call writes it.
| Language | A default is | Evaluated | What it costs there |
|---|---|---|---|
| C++, Swift | any expression | at each call, at the call site | the expression is part of the interface; a header or module interface carries it |
| Kotlin | any expression | at each call, inside the callee | a synthetic $default function and a bitmask per call |
| Python | any expression | once, when the def runs |
one shared object per default — the mutable-default trap |
| C# | a compile-time constant | while compiling the callee | the constant is copied into each caller; changing it needs callers rebuilt |
| Rust | — | — | no defaults; a builder or Option<T> instead |
What transfers: C#’s shape — a constant, recorded with the callee, copied into the call. What does not: an expression evaluated per call, which makes a default something the ledger cannot show and a caller cannot see.
| Option | Why it lost |
|---|---|
Keep a default is a literal; an option needing more takes a T? and decides in the body |
the unit leaves the signature and the default leaves the ledger, which is what a caller reads |
| Record the expression and evaluate it in each caller’s build | the caller needs the callee’s bodies, which a ledger does not carry; and cli::args is a build-time input (ADR-287 D14), so two packages could compute two defaults for one declaration |
| Evaluate at run time, per call (Kotlin, C++) | the default is no longer a value anyone can be shown, and a default may then do things |
Put Duration on ADR-311 D1’s list |
one case in the compiler per std type; the next type is the same question again |
| Read an opaque type’s parts from the Rust type’s fields | Duration’s fields are private to Rust’s std, and the language below promises no layout (ADR-311 D16) |
Give the evaluator a table of std functions it implements itself |
a hand-kept allowlist, which ADR-287 rejected because it rots |
comptime’s initialiser may be.A literal stays the common case. Beside it, a default may be anything Part II
10.2 lets a comptime hold, evaluated under the same rule
(ADR-287 D13-D14) by the same function (D10):
timeout: time::Duration = 30.seconds(), a struct literal, a call to a
function of the program, an item-level comptime of the package. A default is
a comptime without a name.
Not at the call, not at run time. The declaration is where the author sees the value, and the build that has the bodies is the one that can evaluate them.
The signature column writes the evaluated value: a literal as today, a
struct as a struct literal of literals, an opaque std type as D5’s
constructor over literal parts. A caller reads a value and copies it, as it
copies a literal now; it evaluates nothing, so it needs no bodies and no build
parameters of the callee’s package. A ledger whose defaults are all literals is
unchanged.
comptime crosses in.ADR-311 is the rule, unchanged: a value arrives in its view
form, a value that owns memory is refused by what it is (NK1167), a
struct and an enum variant are written field by field against their
declaration. The compiler writes it into each call that leaves the option out,
as it writes a literal now.
std type whose parts the compiler cannot walk crosses through the parts its ledger entry names: the constant column.A type entry may carry constant: a constructor and the parts it takes, each of
a type that itself crosses.
[type."time::Duration"]
constant = "time::Duration::new(secs: u64, nanos: u32)"
The build-time evaluator holds a value of such a type as its type and its parts;
the crossing writes the constructor over the parts as literals. The constructor
is an entry of std like any other, a const fn in std’s Rust half so that a
comptime of the type is a const too, and not pub: it is the type’s
definition for the compiler, not a second spelling for a program
(ADR-150 D2). A std type whose entry has no constant does not
cross, and is refused with NK1167 naming the type. It is the column
ADR-123’s crosses is for threads: an answer for a type whose
parts are the library’s, written once in std and never a case in the compiler.
std function runs at build time when the ledger lets it.Changed by ADR-321 D1-D2: build-time code is compiled, so a std function runs whatever language its body is in.
Build-time code is compiled against std (ADR-321), so
30.seconds() runs from std’s Rust half and is the same value whether the
build computes it or the program does. What decides is
ADR-287 D13-D14’s rule, read off the ledger — never a table of
functions an evaluator implements beside std.
comptime is, at the default.NK1127 cannot evaluate it, NK1152 the rule forbids a callee, NK1167 it
owns memory or names a type with no constant, NK1168 a ring through an
item-level comptime. The span is the default, and the message names the
option. The parse no longer refuses a default for not being a literal.
timeout: time::Duration = 30.seconds(),
and a caller in another package reads 30 seconds in the ledger, not a
number whose unit is a convention.std type by its constant column, and both by ADR-311.comptime gains the same: comptime TIMEOUT: time::Duration = 30.seconds()
lands as a const.time moves
from Rust into std’s Nikaia half. And the ledger gains a spelling no
program writes: an opaque type’s constructor over its parts.1.minutes() + 30.seconds()
as a default. It needs the parts read back in Nikaia, which nothing here
provides.constant column for a type a program describes (nikaia describe,
a foreign type). D5 is std’s.std types get one. A type gets its column when something
needs it to cross; a clock reading (Instant) never does.