RFA-504 · Case file with fixtures · Case 476 of 694 · Compiler evidence
Rust Compound Assignment Needs a Place, Not a Temporary Value
Compound assignment reads, combines, and writes back through a mutable place expression. Literals and computed temporary values have no caller-visible storage destination to update.
- 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
- Compound assignment reads and writes back through one storage location, while a literal or computed value provides no caller-visible destination for the update.
- First discriminating check
- Identify which owner should retain the result, bind or access its mutable place once, and separately verify mutability, borrowing, and operator support.
I wrote 12 += 1. Rust emitted E0067 because compound assignment needs a mutable place on the left, while the literal 12 is only a value.
The failing fixture is intentionally obvious. More realistic forms hide the same issue behind function calls, arithmetic expressions, casts, or macros.
A place represents a memory location
The Rust Reference separates place expressions from value expressions. A local variable, field access, indexing operation, or dereference can identify storage. A literal and most computed expressions produce values.
Compound assignment must read the current left value, combine it with the right operand, and store the result back. Without an identifiable place, the final write has nowhere meaningful to go.
The official E0067 page repairs the issue with a mutable local.
The repaired fixture gives the value an owner
The repaired fixture stores 12 in total, declares the binding mutable, then performs total += 1. The assertion observes the updated location.
This shows two requirements that are easy to mix: the left side must be a place, and that place must permit mutation. A local without mut is a place but not a mutable one, producing a different diagnostic.
I identify which requirement failed before editing ownership or mutability.
A function result is usually not the state to update
Code like current_total() += 1 tries to mutate a returned value rather than the storage from which it may have been copied. Even if a temporary location exists during evaluation, updating it would not necessarily change the source object.
The API should return a mutable reference when callers are meant to update underlying state, or expose a method such as increment() that owns the mutation. Otherwise I bind the returned value locally and accept that I am changing only my copy.
The correct repair depends on which state should persist.
Indexing and dereferencing can be places
values[index] += 1 can work when indexing provides mutable access. *reference += 1 can work through &mut T or another mutable dereference implementation.
These expressions still face borrowing, bounds, and trait requirements. A valid place is not automatically accessible or mutable. Compound operators may also dispatch through traits such as AddAssign, so the operand types must implement the required operation.
I treat place validity, mutation permission, and operator support as three separate checks.
Macros can erase the destination
A macro accepting an expression may generate $expr += amount. Callers can then pass a literal or computed value even though the expansion requires a place.
I document the macro input as a mutable destination and, when possible, design an API that accepts &mut T instead. A typed function gives clearer errors and makes the ownership requirement visible in its signature.
Expansion inspection is useful when the source line does not itself contain +=.
Assignment is an effect, not a numeric expression
12 + 1 computes a new value and can be used anywhere a value is expected. total += 1 changes existing state and evaluates according to compound-assignment rules.
I use the pure expression when no persistent state needs updating. Introducing a mutable local only to imitate an expression can make simple transformations harder to understand.
The distinction helps me choose between data flow and state change.
Compound assignment evaluates its place carefully
An indexed destination may involve calculations or borrowing. I avoid rewriting target[index()] += value naively into target[index()] = target[index()] + value, because the manual form can evaluate the index twice and create different borrow interactions or side effects. The compound operator has its own evaluation rules and expresses one update.
When debugging, I bind a complicated index or receiver once, then update the clear place. This makes evaluation order visible and often produces a smaller diagnostic without changing which element owns the result.
My E0067 checklist
- Is the left operand a local, field, index, or dereference place?
- Is that place mutable and currently borrowable?
- Which storage should retain the updated value?
- Did a function return a copy instead of a mutable reference?
- Does the operand type implement the compound operator trait?
- Did a macro place an arbitrary expression on the left?
- Would a pure expression describe the operation better?
- Does the repaired assertion observe the intended owner?
The core principle is that += does more than calculate: it writes back. Rust requires a real mutable destination so that this effect has one visible and well-defined owner.