RFA-546 · Case file with fixtures · Case 518 of 694 · Compiler evidence
Rust Array Indexing Cannot Move Out a Non-Copy Element
Indexing does not leave a safe hole in a fixed array. Borrow, clone, consume the complete array, or replace the element through an API that preserves validity.
- 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
- An indexed move would leave an unrepresented uninitialized hole in a fixed-size array that remains in scope.
- First discriminating check
- Borrow or clone intentionally, consume the complete array through a pattern or iterator, or replace the slot with another valid value.
Indexing an array feels like selecting one owned element, but array[index] is a place inside an array that still must remain a fully valid value. Moving a non-Copy element out would leave a hole Rust cannot represent, producing E0508.
The failing fixture tries to move a String from workers[0]. String owns heap storage and is not Copy, so the move cannot be an implicit duplicate.
Safe values cannot remain partially uninitialized
After an indexed move, the array variable would still be in scope, but one element would no longer hold a String. Dropping or iterating the array would have to know which slot became empty.
The ordinary type [String; 2] carries no per-slot initialization bitmap. Safe Rust therefore prevents the move through indexing.
The official E0508 page offers three directions: borrow, clone, or consume through destructuring.
Destructuring consumes the complete array
The repaired fixture writes let [first, second] = workers. Ownership of the entire array enters the pattern, and ownership of every element is accounted for.
No partially valid array remains. Each String has exactly one new owner and will be dropped once.
The Reference documents array and slice patterns. This is a strong repair when the size is known and the function intends to consume all elements.
Borrow when the element is only observed
let first = &workers[0] creates a reference and leaves the array intact. This is cheapest when I need to inspect or pass the string temporarily.
I often accept &str downstream instead of String when the callee does not retain ownership. That removes unnecessary moves and works with array elements, vector elements, string literals, and other borrowed text.
The array remains unavailable for conflicting mutation while the reference is live, but ordinary reads can coexist.
Clone when ownership must be independent
workers[0].clone() creates a second String. The array keeps its original element, and the caller receives an owned copy. This is correct when both lifecycles must continue independently.
It can also hide an inefficient interface in a hot path. Before cloning a large payload, I ask whether an identifier, Arc, or consuming iterator better matches ownership.
Copy types avoid the error because indexing copies the value. I do not make resource-owning types Copy; they cannot safely duplicate ownership by bitwise copy.
IntoIterator consumes arrays cleanly
When every element should be processed, for worker in workers or workers.into_iter() consumes the array and yields owned elements. This scales better than spelling a large destructuring pattern.
The standard library's array documentation lists iteration, mapping, and slice conversion behaviour. Edition and method-resolution history can affect old code, so I verify the actual toolchain when maintaining compatibility-sensitive libraries.
Replace preserves a valid element in the slot
For a mutable array that must remain usable, I can use std::mem::replace(&mut workers[0], replacement) or std::mem::take when Default is meaningful. The operation moves out the old element while immediately leaving a valid new one.
This is not a free extraction: I must choose the replacement semantics and have exclusive access. A fake empty string may be a poor sentinel. An array of Option<String> can model slots that intentionally become empty.
Arrays differ from vectors in available ownership APIs
Vec can remove an element and change its length through remove or swap_remove. A fixed array always has exactly N elements, so it cannot shrink to account for extraction.
If indexed removal is central to the algorithm, a vector, Vec<Option<T>>, or purpose-built slot map may be more honest than a fixed array. Type choice determines which state transitions are representable.
My E0508 checklist
- Is the indexed element non-Copy?
- Do I only need a borrow for observation?
- Must the array and extracted value both continue, requiring clone or sharing?
- Can I consume the entire array through a pattern or iterator?
- Does the array need a meaningful replacement value?
- Would
Option<T>represent an empty slot explicitly? - Is a resizable collection more appropriate for removal?
- Have I accounted for the ownership of every element exactly once?
The core principle is that moving one element must not leave an invalid container behind. I borrow, duplicate intentionally, consume the whole array, or replace the slot with another valid state.