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 changevec
variables. - But it got compile error because
vec
is not mutable. - When passing
vec0
into functionfill_vec
it also transfer or move the ownership frommain
function tofill_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
andvec1
accessible at the same time. -
If we pass
vec0
to functionfill_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 ofvec0
. -
That makes both
vec0
andvec1
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);
abovelet z = &mut x;
we can fix it. -
Because
y
already done with the push sox
is free and can be borrowed byz
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 callingget_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.