Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-559 · Case file with fixtures · Case 531 of 694 · Compiler evidence

A Rust Struct Literal Cannot Invent a Field Outside the Type Definition

Named-field construction follows the exact resolved type schema. Fix spelling or imports, update the owned type deliberately, or translate external fields at a boundary.

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
Named-field construction follows the exact resolved type schema and cannot add map-like keys outside its definition.
First discriminating check
Confirm the fully qualified type and feature set, then fix spelling, evolve the owned schema deliberately, or translate external data at a boundary.

A Rust struct literal is checked against one exact resolved type definition. It is not a map that accepts new keys at construction. An unknown field produces E0560.

The failing fixture constructs Limits with retries and timeout_ms, while the declared struct contains only retries.

First verify which type name resolved

In a small fixture the missing field is obvious. In a workspace, several modules may export structs with the same short name. A glob import, generated module, or dependency upgrade can make the literal refer to a different Limits than the reader expects.

I use editor go-to-definition, temporarily qualify the path, or inspect rustc's type notes before changing fields. Adding a field to the wrong struct can create a larger API mistake.

The official E0560 page begins with the practical checks: verify spelling and whether the field exists.

The repaired type includes the intended domain data

The repaired fixture adds timeout_ms because the example's domain genuinely needs that value. Both construction and later reads now share one schema.

Adding a field affects every direct literal for that struct. Existing construction sites may then report missing-field E0063 until they choose a value. This is useful pressure: callers must decide what timeout means rather than silently receiving an arbitrary default.

For public types, adding a public field can also be a breaking change for exhaustive construction and matching. I evaluate compatibility before editing the definition.

A spelling fix may be the whole repair

Rust often suggests a similarly named field. I compare units and meaning, not only edit distance. timeout, timeout_ms, and deadline may represent different concepts even when they look related.

Renaming at the call site is correct only if the existing field's semantics match. Otherwise a constructor or conversion layer should perform the domain translation explicitly.

The Reference defines struct expressions, including named fields, shorthand, and functional update syntax.

External JSON fields do not need to become Rust fields automatically

Serialization libraries can rename, flatten, ignore, or collect unknown fields according to their configuration. A wire payload's key set is not identical to an internal model by default.

I decode into an explicit transport type and map to domain types when validation or naming differs. This prevents an API provider's new field from forcing changes throughout business logic.

E0560 occurs at compile time for a literal. Unknown fields arriving at runtime need parser policy and tests of their own.

Constructors reduce widespread literal coupling

If callers should not know every field, I keep fields private and expose Limits::new, a builder, or purpose-specific constructors. The type can add internal cached or derived fields later without breaking external literals.

Direct public literals are excellent for simple transparent data structures. They are costly when invariants, defaults, or evolution rules belong to the type.

The Reference's struct items section distinguishes record, tuple, and unit structs and their fields.

Feature gates can change available fields

A field behind #[cfg(feature = "...")] may exist in one build and disappear in another. A literal compiled under a different feature set can therefore produce E0560.

I reproduce with the exact Cargo features and target. For cross-feature construction, constructors can centralise conditional fields and keep callers portable.

Generated schemas need regeneration evidence

When the struct comes from code generation, I do not hand-edit the generated file to add the missing field. I verify the source schema, generator version, and feature inputs, then regenerate and review the resulting API diff. This prevents the next generation run from deleting the repair. A small fixture constructing the expected field set can serve as a contract test between schema production and Rust consumption, especially when different repositories update on separate schedules.

My E0560 checklist

  • Which fully qualified struct type did the name resolve to?
  • Is the field misspelled, renamed, gated, or genuinely absent?
  • Do similarly named fields have the same units and meaning?
  • Should the owned type definition include this data?
  • What compatibility impact would adding a field have?
  • Should external payload fields be translated through a transport type?
  • Would a constructor or builder reduce literal coupling?
  • Am I compiling with the expected target and feature set?

The core principle is that a struct definition is a compile-time schema. I make changes at the boundary that owns that schema instead of treating field names as flexible map keys.