Skip to content
Select theme

How to fix error E0505: cannot move out of a value because it is borrowed

Short answer: a reference to a value was still in use when the value itself was moved. A move hands ownership to someone else, which would leave the reference pointing at a value that no longer belongs here. Finish the read before the move, or clone what the reference needs so no borrow is held across the move.

Compiling that with cargo build prints:

error[E0505]: cannot move out of `config` because it is borrowed
--> src/main.rs:4:24
|
2 | let config = String::from("debug");
| ------ binding `config` declared here
3 | let report = &config;
| ------- borrow of `config` occurs here
4 | let final_config = config;
| ^^^^^^ move out of `config` occurs here
5 | println!("report says {report}");
| ------ borrow later used here
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let report = &config.clone();
| ++++++++
For more information about this error, try `rustc --explain E0505`.

report borrows config, and because report is still used after let final_config = config;, the compiler treats that borrow as alive when the move happens. A borrowed value cannot be moved away — Rust rejects the combination.

A move is how ownership changes hands. After let final_config = config;, the original config binding no longer owns anything — the string now belongs to final_config. But report is a reference into the memory the original binding owned, and it is still expected to read that memory on the next line. If the move went through, report would point at storage whose owner has already changed hands.

String does not implement Copy, so let final_config = config; is a transfer, not a duplicate. Rust’s rule against moving a value while a borrow of it is alive is what turns that dangling reference into a compile error instead of a crash or garbage values.

The message says because it is borrowed because the borrow is the thing already there: the read was still going, and the move walked out from under it.

Like E0502, this error is about borrows that are alive at the time of the move, not about how many & expressions the file contains. A borrow ends at its last use, so finishing the read before the move is enough — no extra scope needed:

This prints:

report says debug
config is now debug

report’s borrow ends at the println! line. By the time the move happens, there is no borrow left to conflict with, so the same two operations in a different order compile fine.

Finish the read first — shown above. Reorder so every use of the reference happens before the move.

Clone what the borrow reads. If the read only needs the value, take an owned copy. The clone holds no borrow, so it can outlive the move:

This prints report says debug, config is now debug. For small types like i32, indexing and field access already copy, so this error can only happen with types that move: String, Vec, HashMap, and the rest.

The shape to recognise: passing a value to a function while a borrow is alive

Section titled “The shape to recognise: passing a value to a function while a borrow is alive”

The most common real-world version is handing ownership to a function — a Vec<String> passed by value, a String fed into std::mem::drop — while a reference into it is still in scope:

The call archive(logs) moves the whole vector into the function, and latest still points into it:

error[E0505]: cannot move out of `logs` because it is borrowed
--> src/main.rs:8:13
|
6 | let logs = vec![String::from("boot ok"), String::from("user login")];
| ---- binding `logs` declared here
7 | let latest = &logs[0];
| ---- borrow of `logs` occurs here
8 | archive(logs);
| ^^^^ move out of `logs` occurs here
9 | println!("latest was {latest}");
| ------ borrow later used here
|
help: consider cloning the value if the performance cost is acceptable
|
7 | let latest = &logs.clone()[0];
| ++++++++
For more information about this error, try `rustc --explain E0505`.

Two ways out, depending on who owns the function:

This prints:

archived 2 entries
latest was boot ok

The clone takes its own copy before the move, so latest no longer borrows the vector. If archive is yours to change, the better fix is to borrow at the call site instead: change the signature to fn archive(logs: &[String]) and call archive(&logs). Then nothing moves, and the error disappears without any copying.

Three neighbours that look similar but carry different codes:

  • Mutating (not moving) the value while a shared borrow is alive is E0502 (cannot borrow as mutable ...). There the second operation is a write through &mut; here it is a move.
  • Using the value after the move with no borrow involved is E0382 (borrow of moved value) — the value was already gone and something tried to read the original binding again.
  • Trying to move out through a reference — let s = *latest; on a &String — is E0507 (cannot move out of borrowed content). There the move fails even with nothing else in scope, because a reference never gives up what it points at.