Question

Understanding “Cannot Move Out of Borrowed Content” in Rust

rustreferencemove-semanticsborrow-checker

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 *line which 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 bytes
  • line.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.

Take Quiz

Syntax and Examples

Core idea

In Rust, methods often signal ownership through their names and signatures:

fn into_bytes(self) -> Vec<u8>
  • self means the method takes ownership
  • this usually causes a move
fn as_bytes(&self) -> &[u8]
  • &self means 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 &String
  • into_bytes() needs a String
  • 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")];
  • lines owns a Vec<String>
  • the vector owns one String

2. Iterate with .iter()

for line in lines.iter() {
  • .iter() yields &String
  • line is 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:

  • 72 for H
  • 105 for i

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:

  • line is &String
  • into_bytes() needs ownership of a String
  • taking the String out 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.

Take Quiz

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() for T

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:

  • s is &String
  • into_bytes() consumes a String

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:

  • user is borrowed
  • user.name would 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

SituationWhat you haveWhat you wantWorks?Typical fix
Read from an item in a collection&Stringinspect textYesas_str(), as_bytes(), chars(), bytes()
Convert borrowed string into owned bytes&StringVec<u8>No, not directlyclone, or change iteration to own the String
Consume items from a collectionStringmove into function/methodYesuse into_iter() or own the value already
Modify borrowed item in place&mut StringmutateYesuse mutable methods like push_str
Take a field out of a borrowed struct&Structmove a String fieldNoborrow the field, clone it, or restructure ownership

.iter() vs .into_iter()

MethodYieldsOwnership effect
.iter()&Tborrows items
.iter_mut()&mut Tmutably borrows items
.into_iter()Tconsumes the collection and moves items out

into_* vs as_*

A useful naming pattern in Rust:

PatternUsually means
into_*consume ownership and convert
as_*borrow a view
to_*create a converted owned value

Examples:

  • into_bytes() consumes String
  • as_bytes() borrows bytes from String
  • to_string() creates a new owned String
Take Quiz

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.

Take Quiz

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 String values.
  • 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.
Take Quiz

Keep learning

Related questions

View more