Huh?
Rules
From the Rust book:
Each value in Rust has a variable that’s called its owner.
There can only be one owner at a time.
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:
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:
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:
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:
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
:
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.
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