In this blog, I write about Web Development, Blockchain, and DevOps. I am a Docker Captain, Public Speaker, and Developer Advocate at daily.dev. Check my YouTube channel.
References and Borrowing
In this article, we will see how to use references in Rust. If you want an introduction to Ownership in Rust, in can check the previous article
In this lesson, we will see:
Ownership and Functions
Return Values and Scope
Introduction to References and Borrowing
Mutable References
Multiple Mutable References
Mutable and Immutable References
Dangling references
References Rules
If you prefer a video version
All the code is available on GitHub (link available in the video description)
Ownership and Functions
When you pass a variable to a function, you move or copy it.
If you move it, as it happens with strings, the variable is no longer valid after the function call.
//ownership and functionsfnmain(){
let i = 5;
call_integer(i);
println!("{}", i);
let s = String::from ("Hello, World!");
call_string(s);
println!("{}", s);
}
Return Values and Scope
When a function returns a value, it gives ownership of the value to the calling function.
We can also have a function that has ownership but gives it back to the calling function.
//ownership and functionsfnmain(){
let s1 = give_ownership();
let s2 = String::from("hello"); // s2 comes into scopelet s3 = take_and_give_back(s2); // s2 is moved into take_and_give_back, it returns s3println!("s1: {}", s1);
println!("s2: {}", s2); // error: value used here after moveprintln!("s3: {}", s3);
}
fngive_ownership() -> String {
let some_string = String::from("hello");
some_string
}
fntake_and_give_back(a_string: String) -> String {
a_string
}
Let's type cargo run -q and see the output:
But if we try to use the s2 variable after the function call, we get an error.
So how can we solve this? Let's see an example.
A first example of getting the ownership back
One first solution might be this: we can return the value passed in input back, using a tuple.
For example, if we want to calculate the length of a string, we can return the string and its length.
fnmain(){
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}", s2, len);
}
fncalculate_length(s: String) -> (String, usize){
let length = s.len();
(s, length)
}
This "works", but it's not the best solution.
This solution is tedious and error-prone.
Rust has a feature called references that allows you to refer to some value without taking ownership of it.
Let's see how to modify the previous example using references.
Introduction to References and Borrowing
Modify the code above to use references instead of taking ownership of the string.
fnmain(){
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}", s1, len);
}
fncalculate_length(s: &String) -> usize{
let length = s.len();
length
}
Explanation: &s1 creates a reference to the value of s1 but does not take ownership of it.
In the signature of calculate_length, s: &String means that s is a reference to a String.
If we type cargo run -q, we get the expected output:
We are getting the same outut as before, but now we are not taking ownership of the string.
Here is a schema of what happens when we pass a reference to a function:
IMPORTANT: The action of passing a reference to a function is called Borrowing.
Mutable References
Can we change the value of a reference?
fnmain(){
let s = String::from("hello");
change(&s);
println!("{}", s);
}
fnchange(s: &String){
s.push_str(", world");
}
But here we get an error:
References are immutable by default.
But we can make them mutable by using &mut instead of &.
Of course, the value of the variable must be mutable as well.
Can we have multiple mutable references to the same variable?
fnmain() {
letmut s = String::from("hello");
let s1 = &mut s;
let s2 = &mut s;
println!("{}, {}", s1, s2);
}
This code is not valid.
This should surprise you. In many programming languages, having multiple references to the same variable is not a problem. But this can lead to problems at runtime.
In Rust, the compiler prevents this from happening.
Of course, we can have multiple mutable references, but in different scopes.
fnmain() {
letmut s = String::from("hello");
{
let s1 = &mut s;
s1.push_str(" world");
} //s1 goes out of scope herelet s2 = &mut s;
s2.push_str("!");
println!("{}", s2);
}
This code is valid.
Mutable and Immutable References
Let's try to mix mutable and immutable references.
fnmain() {
letmut s = String::from("hello");
let s1 = &s; //immutable borrowlet s2 = &s; //immutable borrowlet s3 = &mut s; //mutable borrowprintln!("{}, {}, {}", s1, s2, s3);
//throws error: cannot borrow `s` as mutable because it is also borrowed as immutable
}
This code is not valid: we cannot have a mutable reference while we have immutable references.
But if we use the immutable references before the mutable reference, the code is valid.
fnmain() {
letmut s = String::from("hello");
let s1 = &s; //immutable borrowlet s2 = &s; //immutable borrowprintln!("{} and {}", s1, s2);
let s3 = &mut s; //mutable borrowprintln!("{}", s3);
}
This code is valid because the immutable borrows are not used after the mutable borrow.
Dangling references
A Dangling reference is a reference that points to an invalid memory.
fnmain() {
let s = dangle();
}
fndangle() -> &String {
let s = String::from("hello");
&s
}
In this case, the reference &s is returned, but s goes out of scope at the end of the function.
To solve this problem, we can return the string directly.
fnmain() {
let s = no_dangle();
println!("{}", s);
}
fnno_dangle() -> String {
let s = String::from("hello");
s
}
This code is valid.
References Rules
There are 2 main rules for references:
At any given time, you can have either one mutable reference or any number of immutable references.
References must always be valid.
Conclusion
In this article, we have seen how to use references in Rust.
We have seen how to pass references to functions, how to use mutable references, and the rules for using references.
If you prefer a video version
All the code is available on GitHub (link available in the video description)