Rust / Editorial solution guide
Resolve E0499 and E0502: overlapping borrows
Trace the last use of each reference, separate reading from mutation, and use safe APIs to establish disjoint mutable regions.
Read-only knowledge base ยท No public comments
Recognize the symptoms
E0499 commonly reports a second mutable borrow while an earlier exclusive borrow is still used. E0502 commonly reports an incompatible shared and mutable borrow overlap. Compiler locations show the original borrow, the conflicting access, and the later use that keeps the first borrow relevant. These are compile-time rejections, not runtime race reports.
A programmer may know two indices differ, yet ordinary indexing does not necessarily establish disjointness to the borrow checker. A read-only view retained across push can also conflict even when spare capacity appears to make reallocation unlikely. Diagnose the reference relationship rather than a particular observed capacity.
Understand the likely causes
- Safe borrowing permits shared observation or exclusive mutable access under its rules. A mutable reference is not simply a pointer with write permission that can coexist arbitrarily with other references. A borrow can remain relevant until its last use, so inserting unrelated statements does not make the conflict disappear.
- The requested algorithm may mix observation and mutation unnecessarily, or may need an API that proves regions do not overlap. clone can provide a required snapshot, but raw-pointer conversion transfers obligations into unsafe code and is not a routine repair. RefCell is a different runtime-checked model and can still reject overlapping access.
Trace simultaneous exclusive access
The first rejected example holds two mutable references obtained through the same array before using both. Do not execute it or invent an output. Even though zero and one differ, the indexing expressions do not present a disjoint-slice proof. Check whether the algorithm actually requires both references simultaneously; sequential updates are simpler when no combined operation depends on them.
fn main() {
let mut values = [1u32, 2];
let first = &mut values[0];
let second = &mut values[1];
println!("{first} {second}");
}
Intentionally rejected with E0499; inspect the spans without running.
Use a safe disjointness proof
split_at_mut checks a split position and returns non-overlapping mutable slices. The complete correction updates the first element on each side, then reads the whole array after the mutable borrows are finished. The caller still must choose valid bounds and element indices. If simultaneous references are unnecessary, simply update values[0] and values[1] in separate statements instead. Select the approach that makes the intended invariant easiest to inspect.
fn main() {
let mut values = [1u32, 2];
let (left, right) = values.split_at_mut(1);
left[0] += 10;
right[0] += 20;
println!("{:?}", values);
}
[11, 22]
The split yields one element on each side. Both mutations are valid, and the later shared print sees eleven and twenty-two.
Finish observation before structural mutation
For E0502, move the final read before mutation or compute a small owned summary first. In the second rejected excerpt view is used after push, so the shared borrow cannot end earlier. Printing view before push would permit the later mutation once that borrow is no longer needed. If a snapshot must survive, explicitly own the snapshot and account for its copy cost. Merely adding a block around the mutation cannot invalidate a still-needed reference safely.
fn main() {
let mut text = String::from("lab");
let view = &text;
text.push('!');
println!("{view}");
}
Intentionally rejected with E0502; the final read extends the shared borrow.
Verify the fix
Compile and run the corrected complete program in edition 2024; its line is [11, 22]. Add tests for split positions at zero and length where appropriate, handling empty regions before indexing. Compile the rejected excerpts only when checking expected diagnostic codes; they are never runtime tests.
For a sequential read/write repair, keep a test that performs the intended final mutation and inspects the resulting owned data. A compile success alone may hide that the change accidentally removed a required update.
Keep it from recurring
- Keep shared and mutable phases short, avoid storing element references across structural changes, and use safe disjointness APIs. Review the last use of each borrow before introducing clones or interior mutability.