RFA-501 · Case file with fixtures · Case 473 of 694 · Compiler evidence
Rust Function Calls Require the Exact Argument Count
Ordinary Rust functions have fixed arity: every declared parameter receives one argument. Defaults and optional behaviour must be represented explicitly through values, builders, wrappers, or separate functions.
- 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
- Ordinary Rust functions have fixed arity, so a missing policy argument cannot be silently supplied as an optional or default parameter.
- First discriminating check
- Compare the selected declaration's exact parameter count, then decide whether the caller, a wrapper, an Option value, or a builder should own the missing policy.
I called schedule("index") while the function required both a task and a retry count. Rust emitted E0061 because ordinary function calls must supply the exact number of declared arguments.
The failing fixture is easy to repair mechanically, yet it raises an API question: was the caller wrong, or should the retry policy have a default?
Function arity is fixed at the call site
The official E0061 explanation notes that ordinary Rust functions do not have optional arguments or general variadic arguments. A signature with two parameters creates a call contract with two argument positions.
The Reference on call expressions describes the parenthesised, comma-separated argument operands. Rust checks their count before it can fully compare each argument type.
I read the diagnostic's “takes” and “supplied” numbers first, then inspect the highlighted missing or extra positions.
The direct repair supplies the missing policy
The repaired fixture calls schedule("index", 3). This is right when the caller owns the retry decision and three is meaningful at that call site.
If every caller passes the same value, making them repeat it creates noise and divergence risk. I may instead move the default behind a wrapper such as schedule_default(task) or put retry configuration into a scheduler value.
The best repair preserves ownership of the decision.
Option is not automatic optional syntax
A parameter of type Option<u8> still occupies one required argument position. Callers must pass Some(3) or None. The optionality is in the value, not the function grammar.
This is useful when absence has meaning distinct from a chosen default. None might mean inherit global policy, while Some(0) means disable retries. Replacing both with one omitted argument would hide that distinction.
I choose Option only when “not specified” is a real state.
Builders work for growing configuration
When a function accumulates several policy arguments, a request or builder type often produces clearer calls. Named setters avoid remembering the order of multiple integers and booleans, and defaults can be centralised.
Builders add types and code, so I do not use them for every two-parameter helper. They become valuable when configuration grows independently, when many call sites omit different values, or when validation spans fields.
E0061 after frequent signature changes can be a signal that positional arguments no longer scale.
Extra arguments are not ignored
E0061 also appears when a call supplies too many arguments. Rust does not silently drop extras, because evaluating them may have side effects and ignoring their values would conceal a mismatch.
After a function removes a parameter, I inspect what the old argument expression did. Deleting the argument may also delete logging, mutation, allocation, or error propagation embedded in that expression. API migration requires understanding the value computation, not only reaching the right count.
Methods include a receiver implicitly in call syntax
For value.run(x), the source call shows one explicit argument, while the method declaration includes a receiver plus x. Compiler wording and suggestions account for the method form.
I do not manually add value inside method-call parentheses. If receiver resolution is confusing, the fully qualified call form can expose every input explicitly and show which trait or inherent method was selected.
Closures and callable values also have fixed expected arity through their call traits.
Wrapper functions can preserve a stable call shape
When many callers need the old one-argument form, I can keep it as a wrapper and introduce a more explicit function for custom policy. The wrapper delegates with a documented default. This makes migration gradual and gives search results two clear APIs instead of one function whose meaning changes silently.
I avoid giving both functions vague names. schedule and schedule_with_retries tell callers where the default lives. Deprecation can later guide users if the old choice is no longer safe. The key is that each individual Rust function still has fixed arity.
My E0061 checklist
- How many parameters does the selected function or method declare?
- Did name resolution select the function I intended?
- Is a missing value owned by this caller or by a default policy?
- Would
Optionrepresent meaningful absence? - Has a positional API grown enough to need a request or builder?
- Does an extra argument expression have side effects?
- Did a dependency upgrade change the signature?
- Would fully qualified syntax clarify the selected method?
The core principle is that a Rust call supplies one argument for every declared parameter. When the counts disagree, I fix not only the syntax but also where defaults and policy choices should live.