Error E0505

"cannot move out of because it is borrowed" — How to Fix

Rust error E0505 means you tried to move a value while a reference into it was still alive. Fix it by ending the borrow first, cloning, borrowing the function arg instead, or splitting fields.

The error that catches every Rust beginner exactly once

You're writing a function that does two things. First, it grabs a reference into some data so it can read from it. Then it does some other work that ends up moving the data away. Both of those things look fine in isolation. Together, they trigger this:

error[E0505]: cannot move out of `s` because it is borrowed
 --> src/main.rs:5:14
  |
3 |     let r = &s;
  |             -- borrow of `s` occurs here
4 |     do_something_with(r);
5 |     let s2 = s;
  |              ^ move out of `s` occurs here
6 |     println!("{r}");
  |               --- borrow later used here

The compiler is saying: you took a reference to s, you handed it off, you're going to read through that reference later, and in between you tried to move s somewhere else. If the move had been allowed, r would be pointing into a value that no longer exists. That's a use-after-free in C terms. Rust catches it at compile time and gets out of your way.

This article walks through the four ways to fix E0505, when each one is appropriate, and the mental model that makes the error disappear from your code over time.

What "moved" actually means in Rust

When you write let s2 = s; on a value of type String, you didn't copy the underlying buffer. String is three words on the stack (pointer, length, capacity) and a heap buffer. The assignment copies those three words into s2, then marks s as invalid. After the move, only s2 is allowed to read or drop the buffer. The compiler tracks "is s still usable?" through the rest of the function.

References work alongside this. let r = &s; creates a non-owning view into s. As long as r is alive, s must not move and must not be dropped, because r would suddenly be pointing at a stale or invalid place.

Whenever the compiler detects "you're moving s while a reference into s is still in use," it raises E0505. The borrow has to end before the move can happen.

Fix 1: end the borrow before the move

This is the cleanest fix when it applies. Rust's borrow checker is non-lexical, meaning a borrow only "lives" for as long as it's actually used, not until the end of the scope. So if you can finish using the reference before the move, you don't even have to restructure anything.

fn main() {
    let s = String::from("hello");

    // Take a reference and use it.
    let r = &s;
    println!("{r}");
    // r is no longer used after this line. The borrow ends here.

    // Now the move is fine: nothing references s anymore.
    let s2 = s;
    println!("{s2}");
}

If your real code has the println after the move, just swap the order so the reference is read first.

Fix 2: clone the value

If you really do need the original to stick around for the borrow AND a separate owner downstream, clone:

fn main() {
    let s = String::from("hello");
    let r = &s;

    // .clone() allocates a brand-new String with the same contents.
    // s2 owns its own copy; s is left intact for r to keep referencing.
    let s2 = s.clone();

    println!("{r}, {s2}");
}

Clones cost real time and real memory. Don't reach for them as your first move. But for short strings or small structs, the cost is irrelevant and the code reads cleanly.

Fix 3: borrow instead of moving

Often the move that's getting you in trouble was unnecessary in the first place. If you're handing s off to a function that just needs to read it, the function should take &str (or &String) instead of String. That way nothing moves at all.

// Bad: takes ownership for no reason. Forces the caller to move.
fn shout(s: String) {
    println!("{}", s.to_uppercase());
}

// Good: takes a reference. Caller keeps ownership.
fn shout_ref(s: &str) {
    println!("{}", s.to_uppercase());
}

fn main() {
    let s = String::from("hello");
    let r = &s;

    // shout_ref doesn't move s; it just borrows.
    shout_ref(&s);

    // r is still alive, s is still alive. Everyone wins.
    println!("{r}");
}

The rule of thumb: take ownership in a function only if you genuinely need to keep the value, store it somewhere, drop it early, or modify it. Otherwise, take a reference.

Fix 4: split or restructure the data

Sometimes the borrow is into one field and the move is of a sibling field. The compiler used to be conservative here, but modern Rust splits borrows by field for plain structs:

struct Pair {
    a: String,
    b: String,
}

fn main() {
    let p = Pair {
        a: String::from("hi"),
        b: String::from("there"),
    };

    let r = &p.a;
    // Moving p.b out is allowed because r borrows only p.a.
    // (Note: this only works for direct fields, not through methods.)
    let b = p.b;

    println!("{r}, {b}");
}

If the struct has methods that return references covering the whole struct, the splitting trick doesn't apply. In that case, refactor: separate the two pieces into independent variables, or use indices into a vector instead of references where appropriate.

A real-world shape of the bug

Here's the kind of thing that triggers E0505 in actual code:

// Buggy: returns a reference into the string we're about to move.
fn first_word(s: String) -> &'static str {
    let bytes = s.as_bytes();
    for (i, &b) in bytes.iter().enumerate() {
        if b == b' ' {
            // Slice borrows from s. Then we move s out by returning a slice into it.
            // This won't compile. The fix is to take s by reference.
            return &s[..i];
        }
    }
    &s[..]
}

The fix is to take &str and return &str:

fn first_word(s: &str) -> &str {
    for (i, &b) in s.as_bytes().iter().enumerate() {
        if b == b' ' {
            return &s[..i];
        }
    }
    s
}

This is also where lifetimes start showing up: the returned &str borrows from the input. The compiler infers the lifetime by elision, so you don't have to write it out.

The mental model

After enough of these errors, the rule sinks in: a value cannot be moved while there's a live reference into it. When you see E0505, ask:

  • Is the borrow actually still in use at the point of the move? If not, reorder.
  • Does the function I'm calling really need ownership? If not, change its signature.
  • Do I genuinely need two independent owners? If yes, clone.
  • Am I borrowing one field and moving another? Split the struct or restructure.

E0505 looks scary the first time. By the tenth time, it'll feel like a typo: an obvious local mistake the compiler catches before it hurts you.

Where to go next