Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-506 · Case file with fixtures · Case 478 of 694 · Compiler evidence

Rust Struct Literal Syntax Requires a Struct, Variant, or Union Type

Braced construction follows a resolved data constructor, not a type name's spelling. A scalar alias keeps the underlying scalar representation and constructor rules; use a plain value or define a real newtype.

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
A type alias preserves its underlying type's shape and constructor rules, so an uppercase domain name does not manufacture a field or nominal constructor.
First discriminating check
Resolve the path before the braces, then initialise the underlying alias normally or define a real newtype with the intended fields and invariants.

I created type Counter = u64 and then tried Counter { value: 4 }. Rust emitted E0071 because braces with named fields require a struct, struct-like enum variant, or union constructor. A scalar alias does not create one.

The failing fixture shows how an uppercase alias can visually resemble a nominal data type while keeping the underlying type's construction rules.

A type alias is another name, not a new shape

The Reference on type aliases describes an alias as a new name for an existing type. Counter and u64 are the same type for checking, layout, methods, and construction.

There is no field named value inside u64. Braces cannot invent that field merely because the alias has a domain-oriented name.

The official E0071 page offers the two real directions: initialise the underlying type normally or define an actual struct.

The repaired fixture keeps alias semantics

The repaired fixture writes let counter: Counter = 4. This is right when the alias exists for readability but should remain freely interchangeable with u64.

Every u64 operation remains available, and APIs accepting u64 accept Counter without conversion. That convenience also means the compiler cannot prevent a byte count, timestamp, or unrelated identifier from being mixed with it.

An alias documents intent but does not enforce it.

A newtype creates a real domain boundary

If I want Counter to have its own constructor and prevent accidental mixing, I define a tuple struct such as struct Counter(u64) or a named-field struct. This is a distinct type with its own methods and trait implementations.

A tuple struct is usually constructed as Counter(4). A named-field struct can use Counter { value: 4 }. Field visibility decides whether callers may construct it directly.

The design choice is interchangeability versus enforced domain identity.

Braced syntax follows the resolved item kind

The struct-expression Reference says a struct expression starts with a path to a struct, enum variant, or union item. Name resolution may find an alias, primitive, trait, or module instead.

Therefore I inspect what the path resolves to, especially when several imports use the same final name. Correct spelling is not enough if it names the wrong kind of item.

This connects E0071 to namespace and re-export diagnostics elsewhere in the Atlas.

Enum variants can legitimately use named fields

Event::Ready { value: 4 } is valid when Ready is declared as a struct-like variant. A tuple-like variant needs parentheses and positional arguments, while a unit variant normally uses its path.

Switching between variant shapes changes pattern matching as well as construction. I update both producers and consumers rather than applying brace syntax based on the data's conceptual shape.

The declaration determines the constructor form.

Macros need more than a type token

A generic code generator may receive a type path and assume it can emit { value: ... }. But a path does not tell the macro whether the target is a scalar alias, tuple struct, or named-field struct.

I make constructor shape explicit in macro input or generate through a trait method such as From::from. Compile tests cover aliases and newtypes separately so accidental assumptions remain visible.

Construction syntax also communicates invariants

Public named fields allow callers to choose every field directly. A private tuple field with a checked new function can ensure the wrapped scalar stays within a valid range. Therefore the newtype decision is not merely about satisfying braces.

For a counter, every u64 might be valid and a transparent tuple wrapper may be enough. For a percentage, accepting values above one hundred may be wrong, so I keep fields private and return Result from construction. E0071 can be the moment I decide whether the domain deserves this stronger boundary.

My E0071 checklist

  • What item does the path before the braces actually resolve to?
  • Is it a named-field struct, struct-like variant, or union?
  • Is this only an alias for a scalar or another existing type?
  • Do I want documentation-only naming or enforced domain identity?
  • Would a tuple or named-field newtype be clearer?
  • Does field visibility support the intended construction boundary?
  • Did a re-export make the name resolve to another item kind?
  • Does a macro know the target's constructor shape?

The core principle is that syntax follows type shape, not naming style. An alias keeps the original shape; a real struct or variant creates a constructor that braces can use.