Skip to content
Select theme

How to fix ownership errors with a Vec

Short answer: Indexing never hands you the element, only a place to look at it, so let name = names[0]; will not compile when the element type is not Copy. Borrow it — &names[0] — or take it out with a method that owns it: remove, pop, swap_remove, or std::mem::take.

The error: an element will not move out of a Vec

Section titled “The error: an element will not move out of a Vec”

Every block on this page is a complete src/main.rs. Start from any Cargo project — cargo new vec-own will do, nothing in the output depends on the name — and replace the file one block at a time. This first one fails:

error[E0507]: cannot move out of index of `Vec<String>`
--> src/main.rs:3:16
|
3 | let name = names[0];
| ^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
|
help: consider borrowing here
|
3 | let name = &names[0];
| +
help: consider cloning the value if the performance cost is acceptable
|
3 | let name = names[0].clone();
| ++++++++
For more information about this error, try `rustc --explain E0507`.

Indexing a Vec is sugar for a call to Index::index, which takes &self and returns &T. The expression names[0] is a place behind a shared reference, and moving a String out of a place you do not own is what the borrow checker is for: the Vec owns the buffer, and a String lifted out of the middle of it would leave a hole. The two help: lines are the two cheap ways out — borrow the element, or clone it (when should you clone?) — and borrowing is the first half of this page.

hello, Ada
["Ada", "Grace Hopper"]

&names[0] borrows the element instead of moving it: name is a &String, and the Vec keeps every byte it owns. names[1].push_str(...) writes through the same mechanism, and the let mut names is what makes that legal — a mutable borrow needs a mutable binding. When you need every element rather than one, the same fix at loop scale is for name in &names (how to loop over a Vec without moving it).

Borrowing leaves the element where it is. When the element needs to leave the Vec for good, use a method that takes the Vec by &mut self and gives you the value:

remove(0) took Ada, left ["Grace", "Linus", "Barbara"]
swap_remove(0) took Grace, left ["Barbara", "Linus"]

remove(i) shifts every later element down a slot, so the order survives and the cost grows with the length. swap_remove(i) fills the slot with the last element and pops it off — constant time, but the order does not survive, which is visible above: Barbara is first now. pop() is the same idea from the end, and it returns an Option<T>.

Sometimes the slot has to stay. std::mem::take replaces the element with its type’s default and hands you the old value, so the Vec keeps its length:

took "Ada", left ["", "Grace"]

take needs T: Default — String’s default is the empty string — and a &mut to the element, which is why the Vec binding needs mut as well.

If the element type is Copy — i32, bool, f64 — names[0] copies the element out and the original line compiles, because a copy is not a move (how to tell whether a type will move or copy). The error above is a String problem, not a Vec problem.

One more distinction: indexing an index that is not there panics at runtime. That is a different failure with a different fix — get returns an Option — and it is not an ownership error.