The error that feels like a theft accusation
You are writing a function to extract a username from a user profile. You pass the profile as a reference to avoid moving it, because the caller needs the profile later. Inside the function, you grab the username field. The compiler rejects the code with E0507: "cannot move out of borrowed content."
You didn't steal the profile. You just wanted the username. Why is Rust treating you like a thief?
This error is one of the most common friction points for developers coming from languages with garbage collection. It signals a clash between what the code tries to do and the contract established by the borrow. The fix is usually small, but understanding the error prevents a class of memory bugs that other languages hide until runtime.
What "move out of borrowed content" actually means
Rust enforces a strict rule: a borrow is a promise not to take ownership. When you pass a reference &T to a function, you are saying, "Look at this value, but do not take it." Moving a value is taking ownership. If you move a value out of a reference, you are breaking the promise.
Imagine a library book. A reference is like checking out the book to read it. You can read the pages. You can photocopy a page. You cannot rip a page out of the book and mail it to a friend. Ripping the page out destroys the book's integrity. The book is no longer whole, and when you return it, the librarian will notice the missing page.
E0507 is the compiler stopping you from ripping the page out. It ensures that borrowed data remains intact for the duration of the borrow.
Minimal example
The error triggers when you try to extract an owned value from a reference.
struct User {
name: String,
}
/// Prints the user's name by taking ownership of it.
fn print_name(name: String) {
println!("Name: {name}");
}
fn main() {
let user = User {
name: String::from("Alice"),
};
// user is borrowed here. We promised not to take ownership.
let user_ref = &user;
// E0507: cannot move out of `user_ref.name` which is behind a shared reference
// user_ref.name tries to move the String out of the User struct.
// The struct is borrowed, so we cannot remove its contents.
print_name(user_ref.name);
}
The compiler rejects user_ref.name. Accessing .name on a &User gives you a &String, not a String. To get a String, Rust would have to move the data out of the User struct. Moving invalidates the borrow. The compiler stops you.
Why the compiler blocks this
This error protects memory safety. If Rust allowed you to move a field out of a borrowed struct, the struct would be left in an invalid state. The field would be gone, but the struct would still exist. When the struct goes out of scope, its destructor would try to drop the field that's already gone. That leads to a double-free or a use-after-free crash.
The borrow checker guarantees that as long as a reference exists, the data it points to remains valid and unmodified in ways that break ownership. Moving data out violates that guarantee. E0507 enforces the structural integrity of borrowed values.
Real-world traps
E0507 often appears in code that looks innocent. Two patterns trigger it frequently: working with HashMap lookups and destructuring references.
HashMap lookups
HashMap::get returns Option<&V>. The value is borrowed from the map. If you try to move the value out, you hit E0507.
use std::collections::HashMap;
fn process_config(config: &HashMap<String, String>) {
// config.get returns Option<&String>.
// The value is borrowed from the map.
if let Some(value) = config.get("timeout") {
// value is &String.
// Dereferencing tries to move the String out of the borrow.
// E0507: cannot move out of `*value` which is behind a shared reference
let owned: String = *value;
}
}
The map owns the strings. You only have a reference. You cannot take the string out of the map while the map is still alive. The compiler prevents you from creating a hole in the map's storage.
Destructuring references
Destructuring a reference often triggers E0507 if you try to bind owned variables.
struct Config {
host: String,
port: u16,
}
fn read_host(config: &Config) {
// This pattern tries to move `host` out of `config`.
// config is a reference, so we cannot move fields out.
// E0507: cannot move out of `config.host` which is behind a shared reference
let Config { host, .. } = config;
}
The pattern let Config { host, .. } = config expects config to be owned. Since config is &Config, the pattern fails. You are trying to destructure a reference as if it were the value itself.
How to fix it
The fix depends on what you need. You usually have three options: borrow the field, clone the field, or change the function to take ownership.
Borrow the field
If you only need to read the data or pass it to another function that accepts a reference, borrow the field.
struct User {
name: String,
}
/// Prints the user's name by borrowing it.
fn print_name(name: &str) {
println!("Name: {name}");
}
fn main() {
let user = User {
name: String::from("Alice"),
};
// Borrow the field. &user.name gives us &String.
// Rust coerces &String to &str automatically.
print_name(&user.name);
}
Borrowing keeps the data in place. No move occurs. The original struct remains intact. This is the zero-cost solution. Use it whenever you don't need ownership.
Clone the field
If you need an owned copy and the caller must keep the original, clone the field.
fn main() {
let user = User {
name: String::from("Alice"),
};
// Clone creates a new String on the heap.
// The original String stays in the User struct.
let owned_name = user.name.clone();
// owned_name is independent. user is still valid.
println!("Cloned: {owned_name}");
println!("Original: {}", user.name);
}
Cloning allocates new memory and copies the data. It has a cost. Measure the performance impact if you clone large values in hot loops. For small strings or infrequent operations, cloning is often the pragmatic choice.
Convention aside: The community prefers clone() over to_owned() for explicit copies. to_owned() is a trait method that works on slices and references. clone() is clearer when you are copying a value. Use clone() unless you are converting a &str to String in a generic context where to_owned() is idiomatic.
Take ownership
If the function is the final consumer of the data, change the signature to take ownership.
/// Takes ownership of the User and extracts the name.
fn consume_user(user: User) -> String {
// user is owned. We can move fields out freely.
user.name
}
fn main() {
let user = User {
name: String::from("Alice"),
};
// user is moved into consume_user.
let name = consume_user(user);
// user is no longer valid here.
// println!("{}", user.name); // Error: use of moved value
}
Taking ownership transfers responsibility. The caller can no longer use the value. This is efficient because no cloning occurs. Use this when the data is being consumed or transformed and the caller doesn't need it afterward.
The as_ref() pattern for Option and Result
When working with Option or Result, moving the inner value often triggers E0507. The as_ref() method transforms Option<T> into Option<&T> without moving the value.
use std::collections::HashMap;
fn get_timeout(config: &HashMap<String, String>) -> Option<&str> {
// config.get returns Option<&String>.
// We want to return Option<&str>.
// as_ref() converts Option<&String> to Option<&&String>.
// map() applies the conversion to the inner reference.
config.get("timeout").map(|s| s.as_str())
}
as_ref() is a pro move. It lets you work with references inside wrappers without pulling the value out. It preserves the borrow chain and avoids E0507.
Copy types don't trigger this
Types that implement the Copy trait do not trigger E0507. Copy types are moved by copying bits. The original value remains valid because a copy is made.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 10, y: 20 };
let p_ref = &p;
// i32 implements Copy.
// p_ref.x copies the integer. No move occurs.
let x = p_ref.x;
println!("X: {x}");
println!("Original: {:?}", p);
}
i32, bool, char, and tuples of Copy types all copy automatically. E0507 only applies to types that move, like String, Vec, and custom structs without #[derive(Copy)]. If you see E0507 on a simple integer, check your types. You might be wrapping the integer in a non-copy container.
Decision matrix
Choose the fix based on ownership needs and performance constraints.
Use borrowing (&field) when you only need to read the data or pass it to another function that accepts a reference. This avoids allocation and keeps the original value intact.
Use cloning (.clone()) when the caller needs to keep the original data and you need an owned copy for your own scope. Accept the allocation cost when correctness requires independence.
Use ownership transfer (change parameter to T) when the function is the final consumer and the caller doesn't need the data anymore. This eliminates cloning and moves responsibility cleanly.
Use as_ref() when working with Option or Result and you need to transform the inner reference without moving the wrapper. This preserves the borrow chain and enables chaining operations on borrowed data.
Trust the borrow checker here. E0507 is not a nuisance. It is preventing you from creating holes in borrowed data. The compiler forces you to make ownership explicit. That explicitness is what keeps Rust safe.