Move Semantics

These exercises are adapted from pnkfelix’s Rust Tutorial – Thank you Felix!!!

Further information

For this section, the book links are especially important.

Rustlings

借用

move_semantics1

// move_semantics1.rs
// Execute `rustlings hint move_semantics1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
    let vec0 = Vec::new();

    let vec1 = fill_vec(vec0);

    // let vec1 = fill_vec(&vec0);
    // vec0.push(24); // Try accessing `vec0` after having called `fill_vec()`. See what happens!

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);

    vec1.push(88);

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
}

fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    let mut vec = vec;

    vec.push(22);
    vec.push(44);
    vec.push(66);

    vec
}

Hint

So you’ve got the “cannot borrow immutable local variable vec1 as mutable” error on line 13, right? The fix for this is going to be adding one keyword, and the addition is NOT on line 13 where the error is.

Also: Try accessing vec0 after having called fill_vec(). See what happens!

可变借用

move_semantics2

// move_semantics2.rs
// Make me compile without changing line 13 or moving line 10!
// Execute `rustlings hint move_semantics2` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
    let vec0 = Vec::new();

    let mut vec1 = fill_vec(vec0);

    // Do not change the following line!
    println!("{} has length {} content `{:?}`", "vec0", vec0.len(), vec0);

    vec1.push(88);

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
}

fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    let mut vec = vec;

    vec.push(22);
    vec.push(44);
    vec.push(66);

    vec
}

Hint

So, vec0 is passed into the fill_vec function as an argument. In Rust, when an argument is passed to a function and it’s not explicitly returned, you can’t use the original variable anymore. We call this “moving” a variable. Variables that are moved into a function (or block scope) and aren’t explicitly returned get “dropped” at the end of that function. This is also what happens here. There’s a few ways to fix this, try them all if you want:

  1. Make another, separate version of the data that’s in vec0 and pass that to fill_vec instead.
  2. Make fill_vec borrow its argument instead of taking ownership of it, and then copy the data within the function in order to return an owned Vec<i32>
  3. Make fill_vec mutably borrow a reference to its argument (which will need to be mutable), modify it directly, then not return anything. Then you can get rid of vec1 entirely – note that this will change what gets printed by the first println!

move_semantics3

// move_semantics3.rs
// Make me compile without adding new lines-- just changing existing lines!
// (no lines with multiple semicolons necessary!)
// Execute `rustlings hint move_semantics3` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
    let vec0 = Vec::new();

    let mut vec1 = fill_vec(vec0);

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);

    vec1.push(88);

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
}

fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    vec.push(22);
    vec.push(44);
    vec.push(66);

    vec
}

Hint

The difference between this one and the previous ones is that the first line of fn fill_vec that had let mut vec = vec; is no longer there. You can, instead of adding that line back, add mut in one place that will change an existing binding to be a mutable binding instead of an immutable one :)

move_semantics4

// move_semantics4.rs
// Refactor this code so that instead of passing `vec0` into the `fill_vec` function,
// the Vector gets created in the function itself and passed back to the main
// function.
// Execute `rustlings hint move_semantics4` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
    let vec0 = Vec::new();

    let mut vec1 = fill_vec(vec0);

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);

    vec1.push(88);

    println!("{} has length {} content `{:?}`", "vec1", vec1.len(), vec1);
}

// `fill_vec()` no longer takes `vec: Vec<i32>` as argument
fn fill_vec() -> Vec<i32> {
    let mut vec = vec;

    vec.push(22);
    vec.push(44);
    vec.push(66);

    vec
}

Hint

Stop reading whenever you feel like you have enough direction :) Or try doing one step and then fixing the compiler errors that result! So the end goal is to:

  • get rid of the first line in main that creates the new vector
  • so then vec0 doesn’t exist, so we can’t pass it to fill_vec
  • we don’t want to pass anything to fill_vec, so its signature should reflect that it does not take any arguments
  • since we’re not creating a new vec in main anymore, we need to create a new vec in fill_vec, similarly to the way we did in main

move_semantics5

// move_semantics5.rs
// Make me compile only by reordering the lines in `main()`, but without
// adding, changing or removing any of them.
// Execute `rustlings hint move_semantics5` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

fn main() {
    let mut x = 100;
    let y = &mut x;
    let z = &mut x;
    *y += 100;
    *z += 1000;
    assert_eq!(x, 1200);
}

Hint

Carefully reason about the range in which each mutable reference is in scope. Does it help to update the value of referent (x) immediately after the mutable reference is taken? Read more about ‘Mutable References’:

References and Borrowing > Mutable References - The Rust Programming Language

move_semantics6

// move_semantics6.rs
// Execute `rustlings hint move_semantics6` or use the `hint` watch subcommand for a hint.
// You can't change anything except adding or removing references.

// I AM NOT DONE

fn main() {
    let data = "Rust is great!".to_string();

    get_char(data);

    string_uppercase(&data);
}

// Should not take ownership
fn get_char(data: String) -> char {
    data.chars().last().unwrap()
}

// Should take ownership
fn string_uppercase(mut data: &String) {
    data = &data.to_uppercase();

    println!("{}", data);
}

Hint

To find the answer, you can consult the book section “References and Borrowing”

  1. The first problem is that get_char is taking ownership of the string. So data is moved and can’t be used for string_uppercase data is moved to get_char first, meaning that string_uppercase cannot manipulate the data. Once you’ve fixed that, string_uppercase’s function signature will also need to be adjusted. Can you figure out how?

  2. Another hint: it has to do with the & character.