RFA-563 · Case file with fixtures · Case 535 of 694 · Compiler evidence
A Rust Module Path Must Name the Struct Being Constructed
Modules organise names but are not runtime records. Continue the path to a constructible type or variant, then satisfy its visibility and field contract.
- 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
- The path stops at a namespace container rather than reaching the constructible item whose fields define the value.
- First discriminating check
- Follow or import the exact type or variant path, then address its field visibility, invariants, and generated-code boundary separately.
Curly-brace construction needs a type or variant with named fields. A module can contain such a type, but the module itself has no runtime fields or instances. Rust reports E0574 when the path stops too early.
The failing fixture writes protocol { version: 1 }. protocol is a module containing Header.
A module is a namespace boundary
Modules organise items, control paths, and participate in visibility. They are not values allocated at runtime. There is no “protocol object” implied by the module declaration.
The official E0574 explanation describes the expected constructible kinds: structs, struct-like enum variants, and unions.
The modules Reference describes module items and their role in the crate tree.
The repaired path reaches the Header type
The repaired fixture writes protocol::Header { version: 1 }. Now the path resolves to a struct whose public field matches the literal.
Each :: step matters. protocol selects a namespace; Header selects the constructible item inside it. Editor completion and fully qualified paths are useful when several modules re-export same-named types.
I prefer explicit paths at architectural boundaries and shorter imports where context remains obvious.
Enum names also need a concrete struct-like variant
For enum Message { Data { bytes: Vec<u8> } }, construction uses Message::Data { bytes }, not Message { bytes }. The enum is a type, but its field shape depends on the selected variant.
Patterns follow the same requirement. Matching a struct-like variant needs the full variant path so Rust knows which field set and discriminant are involved.
The Reference defines record construction under struct expressions.
Visibility errors may appear after the path is fixed
A correct type path can expose the next issue: the struct or its fields may be private. That is a separate API boundary, not evidence that the qualified path repair was wrong.
If callers outside the module should construct the value, fields can be public or a public constructor can own validation. I usually prefer constructors when invalid combinations must be excluded.
Re-exports can shorten supported public paths
A module may write pub use protocol::Header;, allowing downstream code to construct Header or crate_name::Header depending on scope. Re-exports are public API and should be intentional.
A refactor that moves a type between internal modules can preserve its public re-export path. This decouples source organisation from the path users depend on.
I verify which path rustdoc presents rather than importing through private implementation modules accidentally.
Names can occupy different namespaces
Rust can sometimes have a module, type, or value with related or identical spellings in distinct namespaces. Error text naming “found module” is evidence about which item resolved in this syntactic position.
I follow the resolution result before renaming code. Adding parentheses or braces cannot turn a namespace into a value; the path must identify the right kind of item.
Generated client modules make this especially easy to miss
Code generators often create a module and a same-purpose request type below it, for example create_user::Request. Documentation may casually call both “create user,” while Rust requires the precise type path. I inspect generated rustdoc or source rather than guess the naming convention. If the path is repeated across application code, I re-export one stable public request type from an adapter module. That keeps generator-specific nesting at the boundary and prevents a provider regeneration from causing widespread E0574 fixes.
A constructor function can make this boundary even more stable. Application code then depends on new_header(version) while the adapter owns the generated module path, field spelling, and validation rules.
My E0574 checklist
- What kind of item does the written path resolve to?
- Does record syntax need a struct, union, or struct-like enum variant?
- Is the desired type inside the named module?
- Does an enum require one additional variant segment?
- Are the type and its fields visible from this module?
- Would a constructor enforce invariants better than public fields?
- Is a stable re-export path preferable to exposing internal organisation?
- Does go-to-definition confirm the item I intended?
The core principle is that modules organise constructible things but are not themselves constructed. I follow the path to the exact type or variant whose fields define the value.