Portal/Notes πŸ“

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.
  • References:

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.

Edit on GitHub

Last updated on

Move Semantics | Faisal Affan