Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-477 · Case file with fixtures · Case 449 of 694 · Compiler evidence

Rust Struct Update Syntax Does Not Apply to Enum Variants

Struct-like enum variants use named fields but remain variants, not standalone struct types. Destructure the old variant, carry required fields explicitly, and construct the selected variant with complete data.

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 enum variants share brace syntax with structs but remain alternatives of one enum rather than standalone struct types eligible for update.
First discriminating check
Narrow the variant, destructure fields that survive, and reconstruct it explicitly or extract independently meaningful shared data into a real struct.

I matched a Schedule::Daily value and tried to rebuild it with Schedule::Daily { enabled: true, ..current }. Rust returned E0436 because functional record update syntax requires a struct base, not an enum value.

The failing fixture shows why brace-shaped enum variants can be misleading. They have named fields, but they are still alternatives of one enum rather than standalone struct types.

Struct update preserves one known struct type

For a struct value, Config { enabled: true, ..old } fills unspecified fields from another Config. The compiler knows both expressions have the same single struct type and can apply field-level move rules.

The functional update Reference restricts the base to the same struct type. A struct-like enum variant does not satisfy that rule.

An enum value may hold a different variant at runtime, even when a surrounding match has narrowed the current arm.

Destructure and reconstruct the variant explicitly

The repaired fixture writes:

match schedule {
    Schedule::Daily { hour, .. } => {
        Schedule::Daily { hour, enabled: true }
    }
}

The match establishes the variant and binds the field that must be preserved. Construction then lists the complete output shape.

This makes it visible which data crosses the state transformation.

Named fields do not make a variant a struct type

The enum Reference describes variants as unit-like, tuple-like, or struct-like constructors within an enum. “Struct-like” refers to field syntax, not independent type identity.

Schedule::Daily cannot be named as a standalone parameter type. The parameter type remains Schedule, which admits every variant.

This distinction explains both update syntax and API modelling choices.

Extract shared data into a real struct when updates dominate

If many variants repeat the same configuration fields and frequently need record updates, I may introduce:

struct DailySchedule {
    hour: u8,
    enabled: bool,
}

enum Schedule {
    Daily(DailySchedule),
}

Now the inner struct can use update syntax and have its own methods. I do this when the data has independent meaning, not only to save one reconstruction line.

The extra type also changes public API and serialization shape unless handled deliberately.

Explicit reconstruction is a schema-change alarm

When a new field is added to the variant, every full construction site must address it. This is often useful for state machines: new data should not silently inherit a value without review.

Struct update syntax deliberately opts into carrying unspecified fields. Since enum transitions can change semantic state, Rust's explicitness encourages me to decide what survives.

I avoid replacing missing fields with Default until their domain behaviour is clear.

Ownership follows the destructured fields

Binding a non-Copy field by value moves it from the old enum into the new one. Ignored fields are dropped with the consumed old value. Borrowing the input would require cloning or creating an output that borrows.

After repairing E0436, I test drop timing and allocation when fields hold buffers, handles, or guards. The syntactic repair can encode a real state transition with lifecycle consequences.

Helper methods can centralise transitions

An inherent method such as schedule.enable() can own the match and reconstruction. Callers then request a domain operation rather than repeat representation details.

This is useful when invariants span variants or when new variants need a defined policy. The method should still have tests for every alternative and preserve exhaustive matching where evolution matters.

My E0436 checklist

For externally tagged serialization, explicit reconstruction may also affect wire compatibility. Renaming the variant, omitting a field, or moving shared fields into an inner struct can change encoded shape even when Rust behaviour remains equivalent. I keep representation tests with fixed fixtures when the enum crosses storage or network boundaries. The compiler verifies that the new Rust value is well formed; it does not prove that older readers interpret its serialized form the same way.

  • Is the update base a standalone struct or an enum value?
  • Which variant has been established by the surrounding match?
  • Which fields should be carried, changed, or dropped?
  • Would explicit reconstruction provide a useful migration alarm?
  • Does shared data deserve its own struct type?
  • What moves, clones, or drops occur during the transition?
  • Should a domain method centralise the state change?
  • Will representation changes affect serialization compatibility?

The core principle is that shared field syntax does not imply shared type semantics. Struct update operates on one struct identity. Enum variants represent alternatives, so I narrow the alternative and rebuild it with an explicit account of the data that survives.