RFA-459 · Case file with fixtures · Case 431 of 694 · Compiler evidence
Rust 2024 Disallows mut Inside an Implicitly Borrowing Pattern
Edition 2024 makes binding-mode transitions explicit. Remove mut when a shared borrow is intended, match the outer reference explicitly when copying a value, or obtain a mutable reference from mutable input for mutation.
- 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
- Edition 2024 reserves explicit binding modifiers for move mode so an inner mut can no longer silently reverse ownership established by outer match ergonomics.
- First discriminating check
- Write the full scrutinee and binding types, then remove mut for reading, match the outer reference explicitly for Copy values, or provide &mut input for mutation.
I compiled let [mut value] = &[String::from("ready")]; in edition 2024. Rust rejected it because mut cannot bind by value inside a pattern that is already implicitly borrowing.
The failing fixture is useful because the word mut can suggest several different intentions: mutate a local binding, mutate the referenced value, or move an owned value and then mutate it. Those are not the same operation.
The outer reference changes the default binding mode
When a reference is matched by a non-reference pattern such as [value], match ergonomics automatically dereference the structural layer and change the default binding mode. With a shared &[String; 1], value becomes &String.
The binding-mode Reference describes this top-down process. Edition 2024 only permits explicit mut, ref, or ref mut modifiers when the default mode is still move.
This prevents one inner modifier from silently resetting ownership established by an outer implicit borrow.
Remove mut when reading through a shared borrow
The repaired fixture says:
let [value] = &[String::from("ready")];
assert_eq!(value, "ready");
Here value is a shared reference. It does not need a mutable binding because the code only reads it.
Adding mut to the variable would not make the underlying String mutable through a shared reference anyway. Mutability of a local reference and mutability of its referent are separate.
Match the reference explicitly when copy-by-value is intended
For Copy contents, I can make the outer reference pattern explicit:
let &[mut value] = &[7_u8];
value += 1;
The & pattern consumes the reference layer while the default mode is move, then value receives a copied u8 local that may be reassigned.
This does not work as a way to move a String out of shared borrowed storage. Copy capability remains part of the ownership contract.
Use mutable input to mutate the element
If the goal is to modify the original element, the input must provide mutable access:
let mut values = [String::from("ready")];
let [value] = &mut values;
value.push('!');
Here match ergonomics gives value type &mut String. The binding itself need not be written mut; the reference permits mutation of its referent.
I state which object should change before choosing syntax.
This is an edition migration signal
The Rust 2024 match-ergonomics guide explains the newly reserved syntax and migration lints. Older-edition code may have allowed modifier placement that changed the default binding mode implicitly.
During an edition upgrade, I run the migration tooling, but I still review each repair. Mechanical syntax can preserve compilation while hiding whether the code meant to borrow, copy, or mutate the source.
Edition boundaries let Rust tighten confusing syntax without changing older crates immediately.
Type annotations reveal the result
When nested references make inference hard to read, I add temporary type assertions or use editor type hints. Knowing whether a binding is T, &T, or &mut T resolves most confusion.
I also reduce nested patterns to one layer. Once the ownership transition is clear, I rebuild the full struct or enum destructure.
This is faster than adding ref, mut, and & experimentally until the compiler accepts something.
My edition-2024 binding checklist
I compare the same reduced fixture under the old and new edition when migration behaviour is surprising. The edition flag belongs to the crate, not to an individual source file in normal Cargo builds, so workspace members can temporarily behave differently during a staged upgrade. Recording the exact edition beside the compiler version makes the evidence reproducible. It also prevents a future reader from concluding that a diagnostic is caused only by a newer compiler binary when the language-edition setting is the actual switch.
- What is the complete scrutinee type, including each reference?
- Where does a non-reference pattern trigger automatic dereferencing?
- What is the default binding mode at the reported identifier?
- Do I want to reassign a local, mutate the referent, or move data?
- Is the inner value Copy if I make the outer reference explicit?
- Does the source provide
&mutwhen mutation is required? - Was this syntax accepted only under an older edition?
- Did migration tooling preserve the intended ownership?
The core principle is that binding syntax should not secretly reverse an ownership decision made by an outer pattern. Edition 2024 makes that boundary easier to see. I begin with the desired data access, then write reference structure and binding modifiers that express it directly.