How to Move Semantics in Rust

Nilesh Katuwal Feb 02, 2024
How to Move Semantics in Rust

In this article, we will learn about the move semantics in Rust.

Move Semantics in Rust

In Rust, all types are mutable, and all move operations are equivalent to a bit copy of the original data at the new location. Move converts any captured variables by reference or mutable reference to captured variables by value.

When threads are involved, the move is commonly used.

Rust removal processes are also destructive. For example, after leaving a variable, the variable is no longer usable in code.

In Rust, a move is consistently implemented by copying the data in the object itself and not destroying the original object. It is never implemented by copying the object’s managed resources or executing custom code.

Rust makes an exception for types that do not require move semantics. Instead, the value contains all the necessary information to represent it, and no heap allocations or resources are managed by the value, such as i32.

These types implement the special Copy trait because copying is inexpensive and the default method for passing to functions or managing assignments:

fn foo(bar: i32) {
    // Implementation
}

let var: i32 = 9;
foo(var); // copy
foo(var); // copy
foo(var); // copy

The default function call uses move semantics for types other than Copy, such as String. In Rust, when a variable is moved, its lifetime ends prematurely.

At compile time, the move replaces the destructor call at the end of the block, so it is an error to write the following code for String:

fn foo(bar: String) {
    // Implementation
}

let var: String = "Hello".to_string();
foo(var); // Move
foo(var); // Compile-Time Error
foo(var); // Compile-Time Error

Clone rust requires implementations, but it is identical for all moves: copy the memory in the value and do not call the original value’s destructor.

And in Rust, with this exact implementation, all types are movable; non-movable types do not exist (though non-movable values do). The bytes must encode information about the resource that the value is managing, such as a pointer, in the new location just as effectively as they did in the previous location.

Rust has a mechanism known as pinning that indicates in the type system that a specific value will never move again, which can be used to implement self-referential values and is utilized in async.

Example:

fn destroy_box(z: Box<i32>) {
    println!("The box that contains {} is destroyed", z);
}

fn main() {
    let a = 5u32;
    let b = a;
    println!("a is {}, and y is {}", a, b);

    let m = Box::new(5i32);

    println!("a contains: {}", m);
    let n = m;
    destroy_box(n);
}

The destroy_box function takes ownership of the heap-allocated memory in the above source code. First, the z is destroyed, and the memory is freed.

m is a pointer to a heap-allocated integer.

Output:

a is 5, and y is 5
a contains: 5
The box that contains 5 is destroyed