Huh?

This is a very rough overview, so consider reading the section on ownership in the Rust book if you want to learn more.

Rules

From the Rust book:

  1. Each value in Rust has a variable that’s called its owner.

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

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

Scope

Let's begin with a quick recap of scope. Scope is essentially where an item (function or value) is valid. As an example, look at the code below:

main.rs
fn main() {
  { // s is not valid here, it’s not yet declared
    let mut s = "hello";   // s is valid from this point forward

    // do stuff with s
  } // this scope is now over, and s is no longer valid
  s = "bye"; // you will get an error: unresolved name `s`
}

Variables are valid when they are in scope and invalid when they are out of scope. Why does this matter? In Rust, when a variable goes out of scope, the memory that it was using is automatically returned.

Moving

If you copy a value into another variable, Rust essentially just moves that value. Take this code for example:

main.rs
fn main() {
  let s1 = String::from("hello");
  let s2 = s1;
  println!("{}, world!", s2);
  println!("{}, world!", s1); // this line will result in an error!
}

If you run that code, you will get this error message:

exit status 101
main.rs:5:26: 5:28 error: use of moved value: `s1` [E0382]
main.rs:5   println!("{}, world!", s1);
                                   ^~
<std macros>:2:25: 2:56 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
main.rs:5:3: 5:30 note: in this expansion of println! (defined in <std macros>)
main.rs:5:26: 5:28 help: run `rustc --explain E0382` to see a detailed explanation
main.rs:3:7: 3:9 note: `s1` moved here because it has type `std::string::String`, which is moved by default
main.rs:3   let s2 = s1;
                ^~
main.rs:3:7: 3:9 help: if you would like to borrow the value instead, use a `ref` binding as shown:
main.rs:    let ref s2 = s1;
error: aborting due to previous error

That error message lets you know that you can no longer access s1 because you moved it to s2. What if we don't want to do that? We can use the clone method if we want to deep copy:

main.rs
fn main() {
  let s1 = String::from("hello");
  let s2 = s1.clone();
  println!("{}, world!", s2);
  println!("{}, world!", s1);
}

However, you do not have to clone if you are dealing with integers (or any of the other types that are defined in Data Types). Integers utilize a Copy trait which allows the value to be usable after the assignment.

fn main() {
  let x = 5;
  let y = x;
  println!("x = {}, y = {}", x, y);
}

Why? Because integers are a known size, copies of the actual value are quick to make. Not the same with Strings and HashMaps.

Ownership and Functions

Functions work similarly to variables: if you pass anything (other than the types defined in Data Types that have a pre-defined size), it will change ownership. Take a look at this example:

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

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here.
    // println!("{}", s); // if you uncomment this out, you will get an error!
    
    let x = 5;                      // x comes into scope.

    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. But since 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.

If you want to play around with it, uncomment out line 6 and see what kind of error you get.

Let's go back to our code from Collections:

main.rs
use std::collections::HashMap;

fn main() {

  let mut scores = HashMap::new();

  scores.insert(String::from("Blue"), 10);
  scores.insert(String::from("Yellow"), 50);
  // if we clone when we call it, we can actually use this function!
  get_score(scores.clone(), String::from("Red"));
  get_score(scores.clone(), String::from("Blue"));
}

fn get_score(scores: HashMap<String, i64>, team_name: String) -> String {
  let score = match scores.get(&team_name) {
                Some(num) => num.to_string(),
                None => "nonexistent".to_string()
              };
  println!("{}'s score is {}", team_name, score);
  score
}

This is not really Rust best practice and this code is likely expensive since we are using clone. So let's rewrite it a bit in a slightly more efficient manner.

main.rs
use std::collections::HashMap;

fn main() {

  let mut scores = HashMap::new();

  scores.insert(String::from("Blue"), 10);
  scores.insert(String::from("Yellow"), 50);

  let scores = get_score(scores, String::from("Red"));
  get_score(scores, String::from("Blue"));
}

fn get_score(scores: HashMap<String, i64>, team_name: String) -> HashMap<String, i64> {
  let score = match scores.get(&team_name) {
                Some(num) => num.to_string(),
                None => "nonexistent".to_string()
              };
  println!("{}'s score is {}", team_name, score);
  scores
}

We're going to dig into that code a bit more in the next section.

Last updated