RFA-466 · Case file with fixtures · Case 438 of 694 · Compiler evidence
A Named-field Rust Struct Is Not Callable With Parentheses
Constructor syntax follows the data declaration: braces for named fields, parentheses for tuple structs and tuple variants, and a bare value for unit structs. Use an explicit factory when defaults or validation are intended.
- 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 structs use brace literals and do not introduce the callable constructor item created for tuple structs and tuple variants.
- First discriminating check
- Read the declaration kind and use braces, tuple parentheses, a unit value, Default, or a validating associated factory according to the real construction contract.
I declared struct Config { enabled: bool } and tried to create it with Config(). Rust emitted E0423 because a named-field struct is not a callable constructor in the value namespace.
The failing fixture shows how constructor syntax is tied to declaration shape. Rust does not treat every type name as a zero-argument factory.
Named-field structs use brace literals
The repaired fixture writes:
let config = Config { enabled: true };
Every required visible field is initialised unless struct update syntax supplies a remainder. This makes the configuration state explicit at creation.
The struct Reference distinguishes named-field, tuple, and unit forms, each with corresponding construction syntax.
Tuple structs create callable constructor items
struct Port(u16); introduces both a type named Port and a value-namespace constructor that behaves like a function. Port(8080) is therefore valid.
Named-field structs do not introduce the same callable item because their fields are labelled and constructed with braces. The namespace Reference explains how tuple constructors can occupy the value namespace alongside the type name.
This is why two structs can share conceptual purpose but require different syntax.
Unit structs are values without parentheses
For struct Marker;, the value is written Marker, not Marker(). Adding parentheses asks Rust to call it.
I read the declaration rather than guessing from whether there are zero data fields. A unit struct and an empty named struct struct Marker {} also have different literal forms.
These details matter for generated code that handles every struct kind.
Use Default when the type defines a default state
If I wanted an ordinary default configuration, Config::default() can be appropriate after implementing or deriving Default.
That is a semantic promise: the type has a sensible baseline. I do not add Default only to imitate Config(). Required security modes, endpoints, or credentials may deserve explicit construction instead.
A dedicated Config::new(...) can validate invariants and keep fields private.
E0423 can indicate the wrong namespace
The E0423 explanation covers several related cases: calling a named struct, forgetting ! on a macro, using module-field dot syntax instead of path syntax, or using an enum type as a value.
I read the “found” item in the diagnostic. The spelling may be correct but resolve to a declaration that cannot serve the requested expression role.
Adding parentheses or braces blindly can move the error without fixing intent.
Constructors and factories have different contracts
A tuple-struct constructor simply packages fields and may be passed as a function to map. An associated factory can perform validation, allocation, or selection and can return Result or another type.
I choose a factory when construction has behaviour and a literal when callers should initialise public structure directly. Syntax communicates this boundary.
Changing a public named struct to tuple shape is therefore more than cosmetic.
Macros need the exclamation mark
If the found item is a macro such as println, the valid invocation is println!(...). Macros occupy their own namespace and expand syntax rather than acting as normal functions.
I do not replace a macro call with a same-named function import merely to satisfy E0423. The diagnostic's item kind tells me what punctuation and path form belong there.
My E0423 checklist
I also check examples copied from another crate version. A library can replace a public tuple struct with a named struct and factory to preserve invariants, or expose a type alias whose underlying constructor is not re-exported under the alias name. Old code may still look plausible. Reading the current declaration and its stability notes is more reliable than inferring construction from the type's printed name. For dependencies, I use the documentation matching the exact resolved version.
- What item kind does the identifier resolve to?
- Is the struct named-field, tuple, unit, or empty named form?
- Does creation need braces, parentheses, or a bare unit value?
- Is a macro invocation missing
!? - Did I use
.where a module path needs::? - Would
Defaultbe a truthful domain promise? - Should a validating associated factory own construction?
- Did a refactor change the representation shape?
The core principle is that a Rust type name is not automatically callable. Declaration kind determines what value-namespace constructor exists. I mirror that shape or use an explicit factory whose behaviour is part of the API.