RFA-719 · Case file with fixtures · Case 691 of 694 · Compiler evidence
Why inline(always(extra)) Is Rejected as a Malformed Rust Attribute
The inline attribute accepts no argument, always, or never. Rust 1.98 checks the complete nested meta item instead of silently leaving extra tokens without meaning.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all targets
- Profiles
- dev, release
Direct answer
What this Rust failure means
- Why it happens
- The inline attribute accepts no input, always, or never; Rust 1.98 validates the complete nested meta item instead of leaving meaningless extra tokens unconsumed.
- First discriminating check
- Compare the complete attribute against the Reference, remove arguments nested inside always, and benchmark separately whether the valid inlining hint helps.
Rust attributes look flexible because they use a general meta-item syntax. That syntax does not mean every attribute accepts every nested shape.
Rust 1.98 rejects this function:
#[inline(always(extra))]
fn hot_path() {}
The compiler reports E0539 and lists the valid forms: #[inline], #[inline(always)], and #[inline(never)]. The nested extra token is not an option passed to always. It has no defined meaning in the inline attribute grammar.
The failing fixture preserves the malformed form. The repaired fixture uses #[inline(always)]. Both are compiled with Rust 1.98.1, so this case records the exact compatibility boundary rather than only repeating the accepted spelling.
Meta-item syntax is only the outer container
The Rust parser can recognize an attribute such as #[name(...)] as a general syntactic structure. After parsing it, the compiler component responsible for name must validate the contents.
For inline, the Rust Reference defines three choices. inline without input is a suggestion. inline(always) asks more strongly for inlining. inline(never) suggests the opposite. There is no second level below always.
An older compiler could parse always(extra) and fail to check that all nested input had been consumed. The apparent acceptance did not create a supported configuration. The extra token was not secretly controlling optimization.
Rust 1.98 tightened argument checking for several built-in attributes. The important lesson is broader than this one spelling: syntactically accepted tokens are not necessarily semantically meaningful tokens.
Why removing only extra is the faithful repair
The smallest source repair is:
#[inline(always)]
fn hot_path() {}
This keeps the only recognized intention in the original attribute. If extra was meant as documentation, I move that explanation into a comment. If it was meant to select a condition, I need a real Rust mechanism such as cfg_attr around complete attributes:
#[cfg_attr(feature = "force-inline", inline(always))]
fn hot_path() {}
That code chooses whether to apply a valid attribute. It does not try to invent parameters inside always.
I do not automatically change the malformed form to plain #[inline]. That would weaken the stated request. I also do not keep the extra token behind allow because E0539 is a grammar error, not an adjustable lint whose ignored input can safely remain.
inline(always) is still not a performance proof
After fixing the syntax, I review whether the attribute should exist.
The Reference describes inline as a hint. Its effect can depend on optimization settings, code-generation units, link-time optimization, function properties, and the backend. always is stronger, but it is not a substitute for measuring the complete program.
Forcing a large function into many call sites can increase binary size and instruction-cache pressure. It can also make compilation slower. In another case, exposing a small wrapper to the caller's optimizer can remove an important abstraction cost. The result depends on the surrounding code.
I therefore treat the compiler fix and the performance decision as two reviews:
- Make the attribute valid and preserve the intended direction.
- Benchmark the representative workload and inspect size or generated code when the decision matters.
Passing compilation proves that Rust understands the request. It does not prove that the request improves latency.
Macros can hide the malformed shape
This error often appears after an upgrade inside generated code. A macro may accept arbitrary tokens and paste them into a built-in attribute:
macro_rules! tuned {
($mode:meta) => {
#[inline($mode)]
fn hot_path() {}
};
}
tuned!(always(extra));
The error points at the resulting attribute or macro invocation, while the actual design bug is that the macro's public grammar is wider than inline's grammar.
I repair such a macro by accepting explicit alternatives rather than an unconstrained meta item. Separate arms for always, never, and a default make invalid states fail near the invocation with a message owned by the macro. This also prevents configuration strings or generated tokens from becoming accidental compiler inputs.
If the attribute comes from a procedural macro, I inspect the expanded code. The producer should emit the documented built-in form and validate its own options before generation.
How I investigate attribute errors after an upgrade
I first reduce the code to one attribute and one legal target item. Then I compare the complete nested syntax against the Reference, not against a blog snippet or an older successful build.
I ask:
- Does the attribute accept no input, a single word, or a list?
- Are nested parentheses documented at this exact position?
- Is a macro generating or forwarding the tokens?
- Was the ignored token supposed to have an effect?
- Does the repaired optimization hint still justify itself in measurements?
This keeps me from solving only the surface error. If a project believed extra changed inlining for years, removing it should trigger a check of the original performance assumption.
The core principle is strict configuration. A compiler option which accepts meaningless input is dangerous because readers can believe the input changes behavior. Rust 1.98 turns that ambiguity into a precise error. The valid #[inline(always)] spelling is shorter, but its real benefit is that every remaining token has a defined contract.