Skip to main content

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.