RFA-503 · Case file with fixtures · Case 475 of 694 · Compiler evidence
A Rust Struct Literal Must Initialize Every Required Field
Rust does not leave omitted struct fields uninitialised or invent per-field defaults. Supply every field, use a complete base value, or centralise policy in a constructor or Default implementation.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all Rust targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- Rust does not invent field defaults during literal construction, and safe ordinary struct values must have every field initialised before use.
- First discriminating check
- Find the resolved type's complete field list, choose the missing value by domain meaning, or use an explicit valid base, constructor, or builder.
I constructed Limits { retries: 3 } but the type also contained timeout_ms. Rust emitted E0063 because a struct value cannot exist with one ordinary field left uninitialised.
The failing fixture often appears after a type gains a field. Every direct literal now has to decide what that new part means.
Rust requires a complete value
For a named-field struct, construction provides each field exactly once, either explicitly or through functional update syntax from another complete value. Rust does not fill omitted fields with zeroed memory, null, or a type default automatically.
The official E0063 explanation states the direct rule. The safety benefit is important: safe code does not later read an uninitialised ordinary field.
Completeness is checked before the value can escape into the program.
The direct repair supplies the missing field
The repaired fixture adds timeout_ms: 500 and asserts both fields. This is right when the call site owns both values and the literal remains readable.
I avoid choosing 0 only because it type-checks. Zero timeout might mean no timeout, immediate timeout, or an invalid configuration. The newly required field forces a semantic decision.
Tests should cover that decision, especially for policy values.
Default is explicit, not magical
If a type implements Default, I can write Limits { retries: 3, ..Limits::default() }. The base expression provides every field I do not name.
This remains explicit at the call site. Rust does not invoke Default just because fields are missing. The distinction prevents an innocent-looking literal from running hidden construction logic or adopting defaults the caller did not expect.
I use this pattern only when the default value is safe and unsurprising.
Constructors centralise invariants
For a type with related fields, direct public literals can scatter validation and defaults across the codebase. A constructor such as Limits::new(retries, timeout) or a builder can enforce ranges and relationships once.
Private fields force external callers through that boundary, while internal literals remain possible. This is useful when adding a field should not require every consumer to understand its raw representation.
The tradeoff is API surface and migration design, not only typing convenience.
Adding a public field is still an evolution event
Even if a struct and all its fields are public, downstream code may construct it directly. Adding a field breaks those literals with E0063. Marking a public struct #[non_exhaustive] changes what external crates may construct and match, so it must be considered early.
Inside one crate, I update literals based on their role. A test fixture may need an explicit new value; a production constructor may provide central policy.
I do not apply one default mechanically across every call site without reviewing intent.
Serde defaults are a separate layer
Deserialisation frameworks can define what happens when an input field is absent. That does not change Rust struct literal rules. The framework eventually constructs a complete Rust value according to its configured policy.
I keep wire compatibility decisions in the deserialisation layer and in-memory completeness in the type. Confusing them can make direct constructors behave differently from decoded values.
Tests should compare both paths when consistent defaults matter.
Enum variants follow the same completeness rule
A struct-like enum variant has named fields and must initialise each of them. The missing field belongs to that variant's payload, not to every variant of the enum. This matters during migrations: adding context to an error variant breaks only constructors of that variant, while other cases remain valid.
I update matching patterns too when consumers destructure the changed payload. Using .. in a pattern may intentionally ignore new fields, but construction still needs a complete value unless a proper base mechanism is available. Producers and consumers have different completeness rules.
My E0063 checklist
- Which fields does the resolved struct or variant require now?
- What does the missing field mean in this call site's domain?
- Is zero or empty actually valid, or merely convenient?
- Would an explicit complete base value be appropriate?
- Is
Defaultsafe and unsurprising for this type? - Should a constructor or builder centralise invariants?
- Did a dependency add a public field and break direct literals?
- Do deserialisation and direct construction use compatible policy?
The core principle is that every Rust struct value is fully initialised. E0063 is the point where I must decide the missing field's meaning instead of inheriting an invisible or accidental default.