Rust with diagrams: Ownership 5 - Return Values & Scope

Rust with diagrams: Ownership 5 - Return Values & Scope

Rust documentation with diagrams to fast learn and remember. Let's learn together in Chapter 4.1.

Until now, we discussing about Rust memory approach and how variables & data interacting on Stack & Heap. It's time to learn about Ownership Rust concept.

What is Ownership?

Ownership is a set of rules that govern a Rust program manages memory. All programs have to manage the way they use a computers memory while running. Other languages have garbage collector that regularly looks for no-longer-used memory as the program runs. In Rust, memory is managed through a system of ownership with a set of rules that the compiler checks.

Ownership rules

  1. Each value in Rust has an Owner.

  2. There can only be one owner at a time.

  3. When the owner goes out of scope, the value will dropped.

What is the variable scope?

Variable scope refers to the portion of the code where a variable is accessible and can be used.

The variable is valid from the point at which it's declared the end of the current scope.

Let's see an example:

    { // s is not valid here, it’s not yet declared
        let s = "hello";   // s is valid from this point forward

        // do stuff with s
    } // this scope is now over, and s is no longer valid

Ownership & Functions

If you recall the second Ownership rule mentioned in this article: "There can only be one owner at a time". This mean that Ownership will be moved and returned (if needed) by functions.

To better understand how ownership works in functions, let's examine a code example that implements the concepts discussed in all the "Ownership" articles written before, including data interaction with Heap & Stack and scope.

Let's see it:

fn main() {
    let s = String::from("hello");  // s comes into scope (assigned into Heap)

    takes_ownership(s);             // s's value moves into the function... (data interacting with move s is invalidated)
                                    // ... and so is no longer valid here 

    let x = 5;                      // x comes into scope (assigned to Stack)

    makes_copy(x);                  // x would move into the function,
                                    // but i32 is Copy, so it's okay to still
                                    // use x afterward

} // Here, x goes out of scope, then s (last in, fist out). But because s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

Return Values & Scope

Let's examine more examples to understand scope returns:

fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return
                                        // value into s1

    let s2 = String::from("hello");     // s2 comes into scope

    let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                        // takes_and_gives_back, which also
                                        // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
  // happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {             // gives_ownership will move its
                                             // return value into the function
                                             // that calls it

    let some_string = String::from("yours"); // some_string comes into scope

    some_string                              // some_string is returned and
                                             // moves out to the calling
                                             // function
}

// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                      // scope

    a_string  // a_string is returned and moves out to the calling function
}

Is good to know that Rust does let us return multiple values using a tuple, let's see an example:

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(); // len() returns the length of a String

    (s, length)
}

Conclusion

Keep in mind how ownership is moved or returned to handle it correctly assumes a lot work for a concept that should be common. That's why Rust has a feature for using a value without transferring ownership, called references.

Next article

Once here, we can check 4.1 Chapter how learned. In next article i will summarize references and borrowing concept.

Read next article


I wish that this content helps you to learn Rust while i do it. If you have doubts, suggestions or you see errors, please don't be shy and comment on them. The goal of this content is learn together!

References: