Move Semantics

  • Each value in Rust has an owner.

  • There can only be one owner at a time.

  • When the owner goes out of scope, the value will be dropped.

  • When assigning a value to another variable or passing it to a function, ownership is transferred (moved).

  • Borrowing lets you access a value without transferring ownership, using references (&).

  • You can have only one mutable reference or any number of immutable references at a time.

move_semantics1.rs

// TODO: Fix the compiler error in this function.
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
    let mut vec = vec;

    vec.push(88);

    vec
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn move_semantics1() {
        let vec0 = vec![22, 44, 66];
        let vec1 = fill_vec(vec0);
        assert_eq!(vec1, vec![22, 44, 66, 88]);
    }
}
  • In this exercise the function fill_vec tried to change vec variables.

  • But it got compile error because vec is not mutable.

  • When passing vec0 into function fill_vec it also transfer or move the ownership from main function to fill_vec function.

  • So fill_vec as the owner can do anything with it including change the mutability.

  • Then we can easily add mutable in here let mut vec = vec; to fix the code.

  • And that makes vec is mutable and we can push value into it.

move_semantics2.rs

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

    vec.push(88);

    vec
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    // TODO: Make both vectors `vec0` and `vec1` accessible at the same time to
    // fix the compiler error in the test.
    #[test]
    fn move_semantics2() {
        let vec0 = vec![22, 44, 66];

        let vec1 = fill_vec(vec0.clone()); // pass cloned vec0

        assert_eq!(vec0, [22, 44, 66]);
        assert_eq!(vec1, [22, 44, 66, 88]);
    }
}
  • In this exercise the task is to make both vec0 and vec1 accessible at the same time.

  • If we pass vec0 to function fill_vec it will transfer or move the ownership.

  • That make the vec0 variable invalidated and we got error:

    borrow of moved value: `vec0`
  • So instead of passing vec0 we pass a clone of vec0.

  • That makes both vec0 and vec1 valid.

move_semantics3.rs

// TODO: Fix the compiler error in the function without adding any new line.
fn fill_vec(mut vec: Vec<i32>) -> Vec<i32> { 
    //      ^^^ add mut
    vec.push(88);

    vec
}

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn move_semantics3() {
        let vec0 = vec![22, 44, 66];
        let vec1 = fill_vec(vec0);
        assert_eq!(vec1, [22, 44, 66, 88]);
    }
}
  • This exercise is similar with move_semantics1.rs.

  • But instead of changing the mutability by redeclare variables with mut we do it inside the function parameters.

move_semantics4.rs

fn main() {
    // You can optionally experiment here.
}

#[cfg(test)]
mod tests {
    // TODO: Fix the compiler errors only by reordering the lines in the test.
    // Don't add, change or remove any line.
    #[test]
    fn move_semantics4() {
        let mut x = Vec::new();
        let y = &mut x;
        y.push(42); // move this line above z
        let z = &mut x;
        z.push(13);
        assert_eq!(x, [42, 13]);
    }
}
  • You can have only one mutable reference or any number of immutable references at a time.

  • So the original code have compile error because of x have more than one mutable reference.

    error[E0499]: cannot borrow `x` as mutable more than once at a time
      --> exercises/06_move_semantics/move_semantics4.rs:13:17
       |
    12 |         let y = &mut x;
       |                 ------ first mutable borrow occurs here
    13 |         let z = &mut x;
       |                 ^^^^^^ second mutable borrow occurs here
    14 |         y.push(42);
       |         - first borrow later used here
  • By simply moving y.push(42); above let z = &mut x; we can fix it.

  • Because y already done with the push so x is free and can be borrowed by z in the next step.

move_semantics5.rs

#![allow(clippy::ptr_arg)]

// TODO: Fix the compiler errors without changing anything except adding or
// removing references (the character `&`).

// Shouldn't take ownership
// add & to borrow instead
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}");
}

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

    get_char(&data);

    string_uppercase(data);
}
  • If we look at the comment the function string_uppercase it shouldn't take ownership but the original code does it.

  • So we just need to add reference data: &String to the parameter and pass &data when calling get_char.

  • That makes it borrow instead of move.

  • For string_uppercase remove the reference from data parameters because we want to move the ownership to it and remove the reference too when calling it.

Last updated