A brief on scopes

Before we go further into ownership, we need to get a brief idea of scopes, which might be familiar to you already if you know C, but we'll recap it here in the context of Rust, as ownership works in tandem with scopes. So, a scope is nothing but an environment where variables and values come into existence. Every variable you declare is associated with a scope. Scopes are represented in code by braces {}. A scope is created whenever you use a block expression, that is, any expression that starts and ends with braces {}. Also, scopes can nest within each other and can access items from the parent scope, but not the other way around.

Here's some code that demonstrates multiple scopes and values:

// scopes.rs

fn main() {
let level_0_str = String::from("foo");
{
let level_1_number = 9;
{
let mut level_2_vector = vec![1, 2, 3];
level_2_vector.push(level_1_number); // can access
} // level_2_vector goes out of scope here

level_2_vector.push(4); // no longer exists
} // level_1_number goes out of scope here
} // level_0_str goes out of scope here

To help with this explanation, will assume that our scopes are numbered, starting from 0. With this assumption, we have created variables that have the level_x prefix in their name. Let's run through the preceding code, line by line. As functions can create new scopes, the main function introduces a root scope level 0 with a level_0_str defined within it. Inside the level 0 scope, we create a new scope, level 1, with a bare block {}, which contains the variable level_1_number. Within level 1, we create another block expression, which becomes level 2 scope. In level 2, we declare another variable, level_2_vector, to which we push level_1_number, which comes from the parent scope,that is, level 1. Finally, when the code reaches the end of }, all of the values get destructed and the respective scopes come to an end. Once the scope ends, we cannot use any values defined within them.

Scopes are an important property to keep in mind when reasoning about the ownership rule. They are also used to reason about borrowing and lifetimes, as we'll see later. When a scope ends, any variable that owns a value runs code to deallocate the value and itself becomes invalid for use outside the scope. In particular, for heap allocated values, a drop method is placed right before the end of the scope }. This is akin to calling the free function in C, but here it's implicit and saves the programmer from forgetting to deallocate values. The drop method comes from the Drop trait, which is implemented for most heap allocated types in Rust and makes automatic freeing of resources a breeze.

Having learned about scopes, let's look at an example similar to the one we previously saw in ownership_basics.rs, but this time, let's use a primitive value:

// ownership_primitives.rs

fn main() {
let foo = 4623;
let bar = foo;
println!("{:?} {:?}", foo, bar);
}

Try compiling and running this program. You might be in for a surprise as this program compiles and runs just fine. What gives? In the program, the ownership of 4623 does not move from foo to bar, but bar gets a separate copy of 4623. It appears that primitive types are treated specially in Rust, where they get copied instead of moved. This means that there are different semantics of ownership depending on what types we use in Rust, which brings us to the concept of move and copy semantics.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset