Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

4.3 Ownership and Functions

4.3.0 Before the Main Text

After learning Rust’s general programming concepts, you reach the most important part of all of Rust—ownership. It is quite different from other languages, and many beginners find it difficult to learn. This chapter aims to help beginners fully master this feature.

This chapter has five subsections:

4.3.1 Passing Values to Functions

In terms of semantics, passing a value to a function is similar to assigning a value to a variable, so to put it in one sentence: function parameter passing works the same way as assignment

Next, let’s explain it in detail: passing a value to a function will cause either a move or a copy.

  • For data types that implement the Copy trait, a copy occurs, so the original variable is not affected and can continue to be used.
  • For data types that do not implement the Copy trait, a move occurs, so the original variable is invalidated and cannot be used.

A detailed introduction to the Copy trait, moves, and copies was given in the previous article, 4.2. Ownership Rules, Memory, and Allocation, so it will not be repeated here.

fn main() {
    let machine = String::from("6657");
    wjq(machine);

    let x = 6657;
    wjq_copy(x);
    println!("x is: {}", x);
}

fn wjq(some_string: String) {
    println!("{}", some_string);
}

fn wjq_copy(some_number: i32) {
    println!("{}", some_number);
}
  • For the variable machine:

    • String is a complex data type, allocated on the heap, and it does not implement the Copy trait.
    • When machine is passed to the wjq function, a move occurs, meaning ownership is transferred from the variable machine to the function parameter some_string.
    • At this point, ownership of machine has been transferred. The function wjq can use it normally, but the original variable machine is no longer available. If you try to use machine afterward, the compiler will report an error.
  • For the variable x:

    • i32 is a basic data type with a fixed size, allocated on the stack, and it implements the Copy trait.
    • When x is passed to the wjq_copy function, a copy occurs, meaning the value of x is copied and passed to the function parameter some_number.
    • Because this is just a value copy, the original variable x is unaffected and can still be used after the function call.
  • For the variable some_string:

    • Its scope starts when it is declared on line 10 and ends when the } on line 12 is reached.
    • When it leaves scope, Rust automatically calls the drop function to free the memory occupied by some_string.
  • For the variable some_number:

    • Its scope starts when it is declared on line 14 and ends when the } on line 16 is reached.
    • Nothing special happens when it leaves scope, because types that implement the Copy trait do not call Drop when they go out of scope.

4.3.2 Return Values and Scope

Ownership is also transferred during the process of returning a value from a function.

fn main() {
    let s1 = give_ownership();
    let s2 = String::from("6657");
    let s3 = takes_and_gives_back(s2);
}

fn give_ownership() -> String {
    let some_string = String::from("machine");
    some_string
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string
}
  • The behavior of the give_ownership function:

    • The give_ownership function creates a String variable some_string, and ownership of it belongs to the give_ownership function.
    • When some_string is returned as the function’s return value, ownership is transferred to the caller, namely the variable s1.
    • As a result, some_string will not be dropped after leaving the scope of give_ownership, because its ownership has been handed over to s1.
  • The behavior of the takes_and_gives_back function:

    • The takes_and_gives_back function accepts a String parameter a_string. When the function is called, ownership of the passed-in argument (s2) is transferred to the function parameter a_string.
    • When the function returns a_string, ownership is transferred once again from a_string to the caller, namely the variable s3.
    • At this point, s2 is no longer available, because its ownership has been transferred to takes_and_gives_back, and the function’s return value is assigned to s3.

The ownership of a variable always follows the same pattern:

  • Assigning a value to another variable causes a move. Only types that implement the Copy trait, such as basic types like i32 and f64, are copied during assignment.
  • When a variable containing heap data leaves scope, its value is cleaned up by the drop function, unless ownership of the data has been moved to another variable.

4.3.3 Letting a Function Use a Value Without Taking Ownership

Sometimes the intent of the code is for a function to use a variable, but you do not want to lose the right to use the data as a result. In that case, you can write it like this:

fn main() {
    let s1 = String::from("Hello");
    let (s2, len) = calculate_length(s1);
    println!("The length of '{}' is {}", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length)
}

In this example, s1 has to give ownership to s, but when this function returns, it also returns s intact and hands ownership of the data to s2. In this way, ownership of the data is given back to a variable in the main function, allowing the data under s1 to be used again in main (even though the variable name has changed).

This approach is too troublesome and too clumsy. Rust provides a feature for this scenario called reference, which lets a function use a value without taking ownership of it. This feature will be explained in the next article, 4.4. Reference and Borrowing.