Question
Understanding “Cannot Move Out of Borrowed Content” in Rust
Question
I want to understand the Rust error “cannot move out of borrowed content” (or in newer versions, “cannot move out of ... which is behind a shared reference”).
I have encountered this error many times and usually fixed it, but I never fully understood why it happens.
For example, this code:
for line in self.xslg_file.iter() {
self.buffer.clear();
for current_char in line.into_bytes().iter() {
self.buffer.push(*current_char as char);
}
println!("{}", line);
}
produces an error like:
error[E0507]: cannot move out of borrowed content
--> src/main.rs:31:33
|
31 | for current_char in line.into_bytes().iter() {
| ^^^^ cannot move out of borrowed content
In newer Rust versions, the message is:
error[E0507]: cannot move out of `*line` which is behind a shared reference
--> src/main.rs:31:33
|
31 | for current_char in line.into_bytes().iter() {
| ^^^^ move occurs because `*line` has type `std::string::String`, which does not implement the `Copy` trait
A workaround is to clone line first:
for current_char in line.clone().into_bytes().iter() {
What causes this kind of error, and what is Rust protecting me from when it reports it?
Short Answer
By the end of this page, you will understand why Rust prevents moving a value out of borrowed data, how shared references work, why methods like into_bytes() trigger a move, and which fixes are most appropriate: borrowing, cloning, or changing ownership.
Concept
Rust’s ownership system guarantees memory safety by making sure each value has a clear owner and that references do not outlive or invalidate the data they point to.
The key idea behind this error is:
- A move transfers ownership of a value.
- A borrow only gives temporary access to a value.
- You cannot move a value out of something you only borrowed.
In your example, self.xslg_file.iter() produces references to items, not owned items. If self.xslg_file is something like Vec<String>, then:
for line in self.xslg_file.iter() {
means line is a &String, not a String.
Then this line:
line.into_bytes()
tries to call a method that consumes the String. The method into_bytes(self) takes ownership of the string. But line is only a shared reference &String, so Rust refuses.
That is what the compiler means by:
- cannot move out of borrowed content
- cannot move out of
*linewhich is behind a shared reference
Why this matters:
If Rust allowed moving the String out through a shared reference, the original collection would still think it contains a valid String, but that value would already be gone. That would make the reference invalid and break memory safety.
This is one of the most important ownership rules in Rust:
- If you only borrowed something, you may read it.
- If you want to take ownership, you must actually own it or otherwise replace it safely.
In this specific case, into_bytes() is the wrong method if you only want to inspect the bytes. Better choices are:
line.as_bytes()to borrow the bytesline.bytes()to iterate over bytes without taking ownership
Examples:
for current_char in line.as_bytes().iter() {
self.buffer.push(*current_char as char);
}
or
for current_char in line.bytes() {
self.buffer.push(current_char as char);
}
These work because they borrow the string’s contents instead of consuming the string.
Mental Model
Think of ownership like owning a book.
- If you own the book, you can give it away, tear out pages, or keep it.
- If someone only lent you the book, you can read it, but you cannot give it to someone else.
A shared reference like &String is like borrowing a book from a library reading room. You are allowed to look at it, but not take it away.
Calling into_bytes() is like saying: “I want to take this whole book apart into pages, and I want to own those pages.” Rust says no, because you never owned the book in the first place.
Calling as_bytes() is different. It is like saying: “I just want to look at the pages.” That is allowed.
Syntax and Examples
Core idea
In Rust, methods often signal ownership through their names and signatures:
fn into_bytes(self) -> Vec<u8>
selfmeans the method takes ownership- this usually causes a move
fn as_bytes(&self) -> &[u8]
&selfmeans the method borrows- this does not move the value
Example: code that fails
let names = vec![String::from("Ada")];
for name in names.iter() {
let bytes = name.into_bytes();
println!("{:?}", bytes);
}
Why it fails:
names.iter()gives&Stringinto_bytes()needs aString- Rust will not let you move out of a borrowed
&String
Correct approach: borrow the bytes
let names = vec![String::from("Ada")];
for name in names.iter() {
let bytes = name.as_bytes();
println!("{:?}", bytes);
}
This works because as_bytes() only borrows.
Another correct approach: iterate bytes directly
let names = vec![String::from("Ada")];
for name in names.iter() {
for b in name.bytes() {
println!("{}", b);
}
}
This is often the cleanest option when you only need the byte values.
If you really need ownership
If you truly need to consume each String, iterate by value instead of by reference:
let names = vec![String::from("Ada")];
for name in names {
let bytes = name.into_bytes();
println!("{:?}", bytes);
}
Now name is a String, so moving it into into_bytes() is valid.
Your example rewritten idiomatically
for line in self.xslg_file.iter() {
self.buffer.clear();
for current_char in line.bytes() {
self.buffer.push(current_char as char);
}
println!("{}", line);
}
This avoids cloning and matches the intent better.
Step by Step Execution
Consider this example:
let lines = vec![String::from("Hi")];
for line in lines.iter() {
for b in line.bytes() {
println!("{}", b);
}
println!("{}", line);
}
Step-by-step
1. Create the vector
let lines = vec![String::from("Hi")];
linesowns aVec<String>- the vector owns one
String
2. Iterate with .iter()
for line in lines.iter() {
.iter()yields&Stringlineis a shared reference to the string inside the vector- the vector still owns the string
3. Call .bytes()
for b in line.bytes() {
bytes()borrows from the string- no ownership is transferred
- Rust is happy because the string stays where it is
4. Print each byte
For the string "Hi", the bytes are:
72forH105fori
5. Print the original string
println!("{}", line);
This still works because line was never consumed.
Now compare with the failing version:
for line in lines.iter() {
let bytes = line.into_bytes();
println!("{:?}", bytes);
}
At the into_bytes() call, Rust sees:
lineis&Stringinto_bytes()needs ownership of aString- taking the
Stringout would leave the borrowed location empty
So the compiler stops you before that can happen.
Real World Use Cases
This rule appears constantly in Rust programs.
Reading from collections
When iterating through a Vec<String>, HashMap<K, V>, or similar collection, you often get references:
for item in items.iter() {
// item is borrowed
}
You can inspect items, validate them, log them, or transform borrowed views of them.
Parsing and text processing
When processing strings, you often only need borrowed views:
as_str()as_bytes()chars()bytes()- slices like
&text[..]
These avoid allocations and preserve the original owner.
Web and API handlers
Server code often borrows request data for validation instead of consuming it immediately.
Example uses:
- checking headers
- inspecting JSON fields
- validating input
- logging request data
Configuration and settings
Applications commonly borrow configuration values from a loaded config struct instead of moving them out one by one.
Data pipelines
A parser may walk through records by reference, reading fields and building output without taking ownership unless necessary.
Real Codebase Usage
In real Rust projects, developers usually handle this situation with one of a few common patterns.
1. Borrow instead of move
Most common when you only need to read data.
for line in self.xslg_file.iter() {
for b in line.as_bytes() {
// use b
}
}
This is efficient and avoids cloning.
2. Use iterator methods that match ownership
Choose the right iterator:
.iter()for&T.iter_mut()for&mut T.into_iter()forT
Example:
let lines = vec![String::from("a"), String::from("b")];
for line in lines.into_iter() {
let bytes = line.into_bytes();
println!("{:?}", bytes);
}
This consumes the vector and gives owned strings.
3. Clone only when ownership is truly needed
for line in self.xslg_file.iter() {
let bytes = line.clone().into_bytes();
println!("{:?}", bytes);
}
This works, but it allocates and copies. In production code, cloning is usually a deliberate tradeoff, not the first choice.
4. Use mem::take or Option::take when moving fields out safely
When you own a struct but need to move a field out, Rust often requires you to leave something valid behind.
use std::mem;
struct App {
name: String,
}
let mut app = App { name: String::from("demo") };
let old_name = mem::take(&mut app.name);
This is common in state transitions and builder-like code.
5. Prefer early borrowing in validation code
Validation code usually does not need ownership:
fn is_valid(name: &String) -> bool {
!name.trim().is_empty()
}
More idiomatically:
fn is_valid(name: &str) -> bool {
!name.trim().is_empty()
}
That keeps APIs flexible and avoids unnecessary moves.
Common Mistakes
1. Calling consuming methods on borrowed values
Broken code:
let v = vec![String::from("abc")];
for s in v.iter() {
let bytes = s.into_bytes();
println!("{:?}", bytes);
}
Why it fails:
sis&Stringinto_bytes()consumes aString
Fix:
for s in v.iter() {
let bytes = s.as_bytes();
println!("{:?}", bytes);
}
2. Using .iter() when .into_iter() is needed
Broken code:
let v = vec![String::from("abc")];
for s in v.iter() {
consume_string(s);
}
fn consume_string(text: String) {
println!("{}", text);
}
Fix:
let v = vec![String::from("abc")];
for s in v.into_iter() {
consume_string(s);
}
Use this only if you are okay with consuming the collection.
3. Cloning automatically without understanding why
This works:
for s in v.iter() {
let bytes = s.clone().into_bytes();
}
But it may hide the real issue:
- Did you really need ownership?
- Could a borrowed method work instead?
Prefer borrowing first.
4. Confusing Copy with all data types
Simple types like i32 are Copy, so moving them feels invisible:
let x = 5;
let y = x;
println!("{}", x);
But String is not Copy:
let s = String::from("hello");
let t = s;
// println!("{}", s); // error: moved value
The compiler message often mentions this distinction.
5. Trying to move fields out of a borrowed struct
Broken code:
struct User {
name: String,
}
fn print_name(user: &User) {
let name = user.name;
println!("{}", name);
}
Why it fails:
useris borroweduser.namewould move the field out of borrowed data
Fix by borrowing:
fn print_name(user: &User) {
let name = &user.name;
println!("{}", name);
}
Or clone if needed:
fn print_name(user: &User) {
let name = user.name.clone();
println!("{}", name);
}
Comparisons
| Situation | What you have | What you want | Works? | Typical fix |
|---|---|---|---|---|
| Read from an item in a collection | &String | inspect text | Yes | as_str(), as_bytes(), chars(), bytes() |
| Convert borrowed string into owned bytes | &String | Vec<u8> | No, not directly | clone, or change iteration to own the String |
| Consume items from a collection | String | move into function/method | Yes | use into_iter() or own the value already |
| Modify borrowed item in place | &mut String | mutate | Yes | use mutable methods like push_str |
| Take a field out of a borrowed struct | &Struct | move a String field | No | borrow the field, clone it, or restructure ownership |
.iter() vs .into_iter()
| Method | Yields | Ownership effect |
|---|---|---|
.iter() | &T | borrows items |
.iter_mut() | &mut T | mutably borrows items |
.into_iter() | T | consumes the collection and moves items out |
into_* vs as_*
A useful naming pattern in Rust:
| Pattern | Usually means |
|---|---|
into_* | consume ownership and convert |
as_* | borrow a view |
to_* | create a converted owned value |
Examples:
into_bytes()consumesStringas_bytes()borrows bytes fromStringto_string()creates a new ownedString
Cheat Sheet
Quick rules
- You can move only from something you own.
- You cannot move out of
&T. - Shared references (
&T) allow reading, not taking ownership. - Mutable references (
&mut T) allow mutation, but still do not automatically allow moving fields out. - If a method takes
self, it usually consumes the value. - If a method takes
&self, it borrows.
Common fixes
If you only need to read
s.as_bytes()
s.as_str()
s.bytes()
s.chars()
If you need ownership
- iterate with
into_iter() - restructure code so you own the value
- clone as a last resort or deliberate tradeoff
Recognize consuming methods
fn method(self) -> ...
This usually means move/consume.
Recognize borrowing methods
fn method(&self) -> ...
fn method(&mut self) -> ...
These borrow instead of move.
Your case
Wrong:
line.into_bytes()
Right:
line.as_bytes()
line.bytes()
Typical compiler meaning
“cannot move out of borrowed content” usually means:
- you have a reference
- some code is trying to take ownership from it
- Rust prevents that because the original owner still expects the value to remain there
FAQ
Why does into_bytes() fail but as_bytes() work?
into_bytes() consumes the String and returns an owned Vec<u8>. as_bytes() only borrows the string’s bytes as &[u8].
Why is line borrowed in a for loop?
Because self.xslg_file.iter() returns references to each item. If the collection stores String, then each line is &String.
Why does cloning fix the error?
Cloning creates a new owned String. You are allowed to consume that clone because you own it.
Is cloning the best solution?
Usually not. If you only need to read data, borrowing is cheaper and more idiomatic.
How can I tell whether a method consumes a value?
Look at its signature. If it takes self, it consumes ownership. If it takes &self, it borrows.
Can I ever move out through a reference?
Not directly in normal borrowing cases. Rust requires the original place to remain valid. You often need to borrow, clone, or use tools like mem::take when you own the container mutably.
Why does the error mention Copy?
Types that implement Copy can be duplicated implicitly, so moving them is trivial. String does not implement Copy because it owns heap memory.
Mini Project
Description
Build a small Rust program that processes a list of strings and prints both the original text and its byte values. The project demonstrates the difference between borrowing data for inspection and consuming data for ownership-based conversion.
Goal
Practice choosing between borrowing and moving by processing strings without triggering “cannot move out of borrowed content”.
Requirements
- Create a vector of at least three
Stringvalues. - Iterate over the strings by reference and print each string.
- For each string, print its byte values without cloning it.
- Add a second example that consumes a separate vector and converts each string into owned bytes.
- Keep both examples valid and compileable.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.