Cookie Consent by Privacy Policies Generator Vegapit
logo

Vegapit

Mutability in Rust for functions and closures

Rust Tue Jan 07 2020 20:33:55 GMT+0000 (Greenwich Mean Time)

The aim of the exercise is simple. Start with an empty vector of unsigned integers - as it does not implement the Copy Trait - and create different types of functions and closures to push a new number to the original vector. Now that the goal is set, let's create and instantiate an empty vector:

let mut v : Vec<u8> = Vec::new();

println!("Address: {:p}, Content: {:?}", &v, &v);
// Address: 0xb62dfffb10, Content: []

We will keep track of the address of the v variable to ensure the same instance is used throughout. Let's introduce the first function that serves the purpose.

Function on a mutable variable

fn push1(mut x : Vec<u8>) -> Vec<u8> {
    x.push(1);
    x
}

v = push1(v);

println!("Address: {:p}, Content: {:?}", &v, &v);
// Address: 0xb62dfffb10, Content: [1]

How does push1 work exactly? Well, we can say based on its signature that:

Calling push1(x) means that x moves to the function's scope, and gets destroyed right after the function has finished executing. So we need to return the modified variable and reassign it if we want to be able to use it in the rest of the program. As the printed output shows, the address has been preserved proving that we worked on the same vector instance.

Function on a mutable reference

fn push2(x : &mut Vec<u8>) {
    x.push(2);
}

push2(&mut v);

println!("Address: {:p}, Content: {:?}", &v, &v);
// Address: 0xb62dfffb10, Content: [1,2]

The function push2 takes a mutable reference as argument. Its signature tells us that:

Since the variable v is of type Vec<u8>, we need to cast it into a mutable reference during the function call by passing &mut v as argument. The vector address remains unchanged based on the very definition of variable referencing. Let's now take a look at closures.

Closure using a mutable variable

let mut push3 = || v.push(3);

push3();

println!("Address: {:p}, Content: {:?}", &v, &v);
// Address: 0xb62dfffb10, Content: [1,2,3]

As opposed to functions, closures have access to variables defined outside their scope. The closure push3 is defined as mutable which means that:

Since the closure works directly on the instance of variable v, the address is indeed the same post execution.

Closure with "move" keyword

let push4 = move || {
    v.push(4);
    v
};

v = push4();

println!("Address: {:p}, Content: {:?}", &v, &v);
// Address: 0xb62dfffb10, Content: [1,2,3,4]

The move keyword creates a closure that takes ownership of the variables defined outside its scope, if they are being used within the closure. We can then say about the closure that:

Similarly to the push1 function, because it took ownership of the variable v, the closure needs to return the modified vector so it can be reassigned and used in the rest of the program.

If you like this post, follow me on Twitter and get notified on the next posts.

Page loaded on Monday, August 3rd 2020 at 10:31:30