BBuzzard

Each value has exactly one owner. When the owner goes out of scope, the value is dropped and its resources are freed. Assigning a String, or passing it to a function, moves it: the old name can no longer be used. That rule is what prevents a double free without a garbage collector.

Types that implement Copy, such as i32 and bool, are duplicated instead of moved (lesson 11). For everything else, return the value if the caller still needs it, or lend it with a reference (lesson 12).

Moving one field out of a struct is a partial move: the other fields stay usable, but the struct as a whole does not.

Assignment moves heap data; Copy types are duplicated

fn demo_assignment() {
    println!("-- assignment --");

    let a = String::from("hello");
    let b = a;

    // println!("{a}");
    // error[E0382]: borrow of moved value: `a`
    println!("b owns it now: {b}");

    let n = 3;
    let m = n;
    println!("ints are Copy: {n} {m}");

    // A move copies only the (pointer, length, capacity) header, not the heap bytes.
    let v = vec![1, 2, 3];
    let p = v.as_ptr();
    let w = v;
    println!("same heap buffer after move? {}", std::ptr::eq(p, w.as_ptr()));
}

Moving into and out of functions

fn take(s: String) {
    println!("took {s}");
}

fn give_back(s: String) -> String {
    s
}

fn add_suffix(mut s: String) -> String {
    s.push_str("_v2");
    s
}

fn demo_functions() {
    println!("-- functions --");

    let s = String::from("hi");
    take(s);

    // take(s); // error[E0382]: use of moved value: `s`

    let t = String::from("there");
    let t = give_back(t);
    println!("got back {t}");

    let name = add_suffix(String::from("file"));
    println!("transformed {name}");
}

Who drops what, and when

struct Noisy(&'static str);

impl Drop for Noisy {
    fn drop(&mut self) {
        println!("  drop {}", self.0);
    }
}

fn consume(n: Noisy) {
    println!("  consume got {}", n.0);
}

fn demo_drop_timing() {
    println!("-- drop timing --");

    let a = Noisy("a");
    let b = Noisy("b");
    consume(a); // a is dropped at the end of consume
    println!("  back in caller");

    let c = b; // moving does not drop
    println!("  moved b into c");

    let _ = &c;
    println!("  end of scope next");
}

Moves in loops and conditionals

fn demo_conditional_move() {
    println!("-- conditional move --");

    let names = vec![String::from("ann"), String::from("ben")];
    for name in names {
        // each iteration takes ownership of one String
        println!("owned {name}");
    }

    // println!("{names:?}"); // error[E0382]: borrow of moved value: `names`

    let s = String::from("maybe");
    let keep = true;
    if keep {
        let moved = s;
        println!("moved into branch: {moved}");
    }

    // s is considered moved here, even though only one branch moved it.
}

Partial moves out of a struct

#[derive(Debug)]
struct Pair {
    name: String,
    tag: String,
    n: i32,
}

fn demo_partial_move() {
    println!("-- partial move --");

    let p = Pair { name: String::from("ada"), tag: String::from("x"), n: 1 };
    let name = p.name;
    println!("{name} {} {}", p.tag, p.n);

    // println!("{p:?}");
    // error[E0382]: borrow of partially moved value: `p`

    // Destructuring moves several fields at once.
    let q = Pair { name: String::from("bob"), tag: String::from("y"), n: 2 };
    let Pair { name, tag, .. } = q;
    println!("{name} {tag} n still readable: {}", q.n);
}

Moving out of a Vec or Option without breaking the owner

fn demo_take_out() {
    println!("-- taking values out --");

    let mut v = vec![String::from("a"), String::from("b"), String::from("c")];

    // let first = v[0]; // error[E0507]: cannot move out of index of `Vec<String>`
    let last = v.pop().unwrap();
    let first = v.remove(0);
    let swapped = std::mem::replace(&mut v[0], String::from("new"));
    println!("{first} {last} {swapped} left {v:?}");

    let mut slot = Some(String::from("token"));
    let token = slot.take();
    println!("{token:?} left {slot:?}");
}
$ cargo run --bin 10_ownership_move
-- assignment --
b owns it now: hello
ints are Copy: 3 3
same heap buffer after move? true
-- functions --
took hi
got back there
transformed file_v2
-- drop timing --
  consume got a
  drop a
  back in caller
  moved b into c
  end of scope next
  drop b
-- conditional move --
owned ann
owned ben
moved into branch: maybe
-- partial move --
ada x 1
bob y n still readable: 2
-- taking values out --
a c b left ["new"]
Some("token") left None

Full program

10_ownership_move/main.rs · 167 lines
View on GitHub
// 10. Ownership and moves
//
// Each value has exactly one owner. When the owner goes out of scope, the value is
// dropped and its resources are freed. Assigning a String, or passing it to a function,
// moves it: the old name can no longer be used. That rule is what prevents a double
// free without a garbage collector.
//
// Types that implement Copy, such as i32 and bool, are duplicated instead of moved
// (lesson 11). For everything else, return the value if the caller still needs it, or
// lend it with a reference (lesson 12).
//
// Moving one field out of a struct is a partial move: the other fields stay usable,
// but the struct as a whole does not.
//
// Run: cargo run --bin 10_ownership_move

// Example 1: assignment moves heap data; Copy types are duplicated.
fn demo_assignment() {
    println!("-- assignment --");

    let a = String::from("hello");
    let b = a;

    // println!("{a}");
    // error[E0382]: borrow of moved value: `a`
    println!("b owns it now: {b}");

    let n = 3;
    let m = n;
    println!("ints are Copy: {n} {m}");

    // A move copies only the (pointer, length, capacity) header, not the heap bytes.
    let v = vec![1, 2, 3];
    let p = v.as_ptr();
    let w = v;
    println!("same heap buffer after move? {}", std::ptr::eq(p, w.as_ptr()));
}

fn take(s: String) {
    println!("took {s}");
}

fn give_back(s: String) -> String {
    s
}

fn add_suffix(mut s: String) -> String {
    s.push_str("_v2");
    s
}

// Example 2: moving into and out of functions.
fn demo_functions() {
    println!("-- functions --");

    let s = String::from("hi");
    take(s);

    // take(s); // error[E0382]: use of moved value: `s`

    let t = String::from("there");
    let t = give_back(t);
    println!("got back {t}");

    let name = add_suffix(String::from("file"));
    println!("transformed {name}");
}

struct Noisy(&'static str);

impl Drop for Noisy {
    fn drop(&mut self) {
        println!("  drop {}", self.0);
    }
}

fn consume(n: Noisy) {
    println!("  consume got {}", n.0);
}

// Example 3: who drops what, and when.
fn demo_drop_timing() {
    println!("-- drop timing --");

    let a = Noisy("a");
    let b = Noisy("b");
    consume(a); // a is dropped at the end of consume
    println!("  back in caller");

    let c = b; // moving does not drop
    println!("  moved b into c");

    let _ = &c;
    println!("  end of scope next");
}

// Example 4: moves in loops and conditionals.
fn demo_conditional_move() {
    println!("-- conditional move --");

    let names = vec![String::from("ann"), String::from("ben")];
    for name in names {
        // each iteration takes ownership of one String
        println!("owned {name}");
    }

    // println!("{names:?}"); // error[E0382]: borrow of moved value: `names`

    let s = String::from("maybe");
    let keep = true;
    if keep {
        let moved = s;
        println!("moved into branch: {moved}");
    }

    // s is considered moved here, even though only one branch moved it.
}

#[derive(Debug)]
struct Pair {
    name: String,
    tag: String,
    n: i32,
}

// Example 5: partial moves out of a struct.
fn demo_partial_move() {
    println!("-- partial move --");

    let p = Pair { name: String::from("ada"), tag: String::from("x"), n: 1 };
    let name = p.name;
    println!("{name} {} {}", p.tag, p.n);

    // println!("{p:?}");
    // error[E0382]: borrow of partially moved value: `p`

    // Destructuring moves several fields at once.
    let q = Pair { name: String::from("bob"), tag: String::from("y"), n: 2 };
    let Pair { name, tag, .. } = q;
    println!("{name} {tag} n still readable: {}", q.n);
}

// Example 6: moving out of a Vec or Option without breaking the owner.
fn demo_take_out() {
    println!("-- taking values out --");

    let mut v = vec![String::from("a"), String::from("b"), String::from("c")];

    // let first = v[0]; // error[E0507]: cannot move out of index of `Vec<String>`
    let last = v.pop().unwrap();
    let first = v.remove(0);
    let swapped = std::mem::replace(&mut v[0], String::from("new"));
    println!("{first} {last} {swapped} left {v:?}");

    let mut slot = Some(String::from("token"));
    let token = slot.take();
    println!("{token:?} left {slot:?}");
}

fn main() {
    demo_assignment();
    demo_functions();
    demo_drop_timing();
    demo_conditional_move();
    demo_partial_move();
    demo_take_out();
}