Rust with diagrams: Ownership 6 - References & Borrowing
Rust documentation with diagrams to fast learn and remember. Let's learn together in Chapter 4.2.
Before reading about References and Borrowing, you should know how variables and data interact with "move" and "clone" and understand the concept of the Heap. You can find a summary of these topics in the next articles.
Rust's memory approach, Data interacting with move & Data interaction with clone
What is a reference? Understanding Borrowing
A reference is like a pointer that it's an address can be follow to access stored at that address.
Unlike a pointer, a reference is guaranteed to point to a valid value of a particular type of the life of that reference.
Ampersands (&) represent references, and they allow you to refer to some value without taking ownership of it. Let's see an example:
fn main() {
let s1 = String::from("hello"); // String with pointer into Heap
let len = calculate_length(&s1); // Here s1 reference is created without taking s1 ownership
println!("The length of '{}' is {}.", s1, len); // Rust can access to s1 because s1 wasn't moved, instead, s1 reference was used
}
fn calculate_length(s: &String) -> usize {
s.len() // not need to return the values in order to give back ownership, because we never had ownership
} // Here, s goes out of scope. But because it does not have ownership of what
// it refers to, it is not dropped.
We call the action of creating a reference borrowing. As in real life, if a person owns something, you can borrow it from them. When you’re done, you have to give it back. ¡You don’t own it!
Mutable & Inmutable References
By default, references are inmutables (read-only), this mean that if we try modify a reference Rust will throws an error. For example:
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world"); // Rust will throw an error in compile time
}
To fix this code, we can modify the "s" variable to mutable. To do this, we have to add a few small tweaks that use. Let's see:
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world"); // Now it compiles
}
Rust avoids Data Races
With the goal of avoid "Data races" and multiple mutable references to the same data at the same time allows for mutation in a very controlled fashion, Rust has next restriction:
if you have a mutable reference to a value, you can have no other references to that value. This code that attempts to create two mutable references to
s
will fail
Data races cause undefined behavior and can be difficult to diagnose and fix when you're trying to track them down at run time. Data races happens when these three behaviors occur:
Two or more pointers access the same data at the same time.
At least one of the pointers is being used to write to the data.
There’s no mechanism being used to synchronize access to the data.
Let's see examples:
Success:
let mut s = String::from("hello");
{
let r1 = &mut s; // no simultaneous -> no problem
} // r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &mut s;
Error:
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
Success:
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
Rust avoids Dangling References
A Dangling Reference is a pointer that reference a location in memory that may have been given to someone else by freeing some memory while preserving a pointer to that memory. Rust compiler will ensure that the data will not go out of scope before the reference to the data does. Dangling Reference example:
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String { // dangle returns a reference to a String
let s = String::from("hello"); // s is a new String
&s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!
The error throwed:
error[E0106]: missing lifetime specifier
We’ll discuss lifetimes in detail later. One option to fix it is return the String directly:
fn no_dangle() -> String {
let s = String::from("hello");
s // Ownership is moved out, and nothing is deallocated.
}
References Rules
Let's recap what we've discussed about references:
At any given time, you can have either mutable reference of any number of inmutable references.
References must always be valid.
Next article
Once we understood References and Borrowing in Rust, we will see a different kind of reference (slices) in the 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: