Rust’s “cannot move out of borrowed content” (a.k.a. “behind a shared reference”) — what it really means and how to fix it without
Ever hit this? error[E0507]: cannot move out of `*line` which is behind a shared reference …and then you slap a .clone() on it, the code compiles, you ship it—and you’re still not sure why it failed in the first place? Let’s demystify this error, understand exactly what’s going on, and then walk through the most idiomatic, zero-allocation fixes. We’ll use your example and expand it into a general mental model you can reuse anywhere.
The setup You have something like: 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);
} The compiler objects at line.into_bytes().
Why the compiler complains 1) What line actually is Inside for line in self.xslg_file.iter(), the iter() very likely yields references to lines—i.e. &String or &str. You don’t own the String; you’re merely borrowing it for the duration of the loop body. 2) What into_bytes asks for The signature: fn into_bytes(self) -> Vec<u8> Note the receiver: self, by value. Calling this consumes the String. Ownership transfers into the function and you get a Vec<u8> back. After that, the original String is gone. 3) Why this is a problem You only have &String (a shared borrow), not a String. You cannot move (consume) something you don’t own. That’s what the error literally says: cannot move out of *line which is behind a shared reference Rust prevents you from invalidating the borrowed String while a borrow is active. 4) Why println!("{}", line) does compile Printing borrows. println! with {} uses the Display implementation, which can work with &str or &String. No ownership transfer, no problem.
Don’t reach for clone first clone “fixes” the error by creating a brand-new String you do own, and then you can call into_bytes() on that clone: for current_char in line.clone().into_bytes().iter() {
/* ... */
} But that allocates and copies the entire string — often unnecessary and sometimes a perf killer. There are cleaner, zero-allocation options.
Optimal solutions (choose by intent) Goal A: You only need bytes (read-only) Use a borrowing API: as_bytes() or bytes(). // Borrow the bytes as a slice: for &b in line.as_bytes().iter() {
// b: u8 // If you truly want a lossy ASCII mapping: self.buffer.push(char::from(b)); // Be careful: not Unicode-aware
} Or more idiomatically: for b in line.bytes() {
// b: u8, iterator borrows the string; no allocations self.buffer.push(char::from(b)); // same ASCII caveat
} ⚠️ Unicode caveat: turning arbitrary UTF-8 bytes into char via as or char::from is only sensible for ASCII. If you intend to copy text, don’t reinvent decoding—see Goal C.
Goal B: You need Unicode characters (not raw bytes) Use chars(), which iterates over Unicode scalar values: for ch in line.chars() {
self.buffer.push(ch);
} No allocation, fully Unicode-aware.
Goal C: You just want to copy the line into buffer Avoid per-char loops entirely: self.buffer.clear(); self.buffer.push_str(line); // borrows; zero allocations if buffer has capacity This is both simpler and faster. If you need to transform later, transform once, not byte-by-byte with allocations.
Goal D: You truly want to consume each String If your logic must take ownership (e.g., you’re going to turn each line into bytes and discard the original), then iterate by value over the owning collection: // Suppose you own a Vec<String> called lines: for line in lines.into_iter() { // moves each String out of the Vec
let bytes = line.into_bytes(); // OK now — you own it /* ... */
} If self.xslg_file.iter() deliberately returns borrowed lines, you can’t consume them. You’d need an API that yields owned Strings (into_iter() on an owned container) or restructure to take ownership first (e.g., let file = std::mem::take(&mut self.xslg_file); and then for line in file.into_iter() { ... }, if that type supports it).
Putting it all together (idiomatic rewrites) 1) Fast & simple copy to buffer (most common) for line in self.xslg_file.iter() {
self.buffer.clear();
self.buffer.push_str(line); // borrow-only; no extra allocation
println!("{line}");
} 2) Process Unicode characters for line in self.xslg_file.iter() {
self.buffer.clear();
for ch in line.chars() {
// e.g., transform/filter characters
self.buffer.push(ch);
}
println!("{line}");
} 3) Process raw bytes (ASCII-only char conversion) for line in self.xslg_file.iter() {
self.buffer.clear();
for b in line.bytes() {
// WARNING: this is lossy for non-ASCII
self.buffer.push(char::from(b));
}
println!("{line}");
}
4) Consume owned lines (when you must move) // If you own a Vec<String>: let lines: Vec<String> = /* ... */; for line in lines.into_iter() {
let bytes = line.into_bytes(); // consume; get Vec<u8> // work with bytes...
}
A quick mental model you can reuse
- Receiver types matter
- fn foo(self) → consumes/owns the value
- fn foo(&self) → borrows immutably
- fn foo(&mut self) → borrows mutably
- Method naming conventions
- into_* → consumes and returns a new owned value
- as_* → returns a borrowed view (zero-copy)
- to_* → creates a new owned value (allocates/copies)
- Iterating a collection
- .iter() → yields &T (borrowed)
- .iter_mut() → yields &mut T (borrowed, mutable)
- .into_iter() → yields T (owned, moves out)
If you have a borrow, you cannot call a method that consumes the value. Pick a borrowing method (as_*, bytes(), chars(), etc.) or change your iteration to produce owned values.
Why Rust designed it this way (and why it’s good) Transferring ownership lets Rust:
- Optimize allocations (no needless clones)
- Enforce correctness at compile time (no use-after-free)
- Make intent explicit (consuming vs. borrowing)
You decide when data should be duplicated, shared, or moved — Rust enforces that decision.
Summary cheat sheet
- Seeing E0507 here? You’re trying to call a consuming method (into_*) on a borrowed value.
- Best fix: Choose a borrowing alternative (as_bytes, bytes, chars, push_str) or iterate by value with into_iter() if you truly need to consume.
- Avoid cloning by default. Reach for clone only when duplication is actually what you mean.
Final “optimal solution” for your snippet If your intent is just to mirror the line into self.buffer and print it, the most idiomatic and efficient version is: for line in self.xslg_file.iter() {
self.buffer.clear();
self.buffer.push_str(line); // borrow-only, no extra copies
println!("{line}");
} If you need per-unit processing, swap push_str(line) for either:
- for ch in line.chars() { self.buffer.push(ch); } // Unicode-aware
- for b in line.bytes() { self.buffer.push(char::from(b)); } // ASCII-only
No clones, no moves from shared references — just clean, fast Rust.