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 functions
fn main(){
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 functions
fn main(){
let s1 = give_ownership();
let s2 = String::from("hello"); // s2 comes into scope
let s3 = take_and_give_back(s2); // s2 is moved into take_and_give_back, it returns s3
println!("s1: {}", s1);
println!("s2: {}", s2); // error: value used here after move
println!("s3: {}", s3);
}
fn give_ownership() -> String {
let some_string = String::from("hello");
some_string
}
fn take_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.
fn main(){
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}", s2, len);
}
fn calculate_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.
fn main(){
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}", s1, len);
}
fn calculate_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?
fn main(){
let s = String::from("hello");
change(&s);
println!("{}", s);
}
fn change(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.
//mutable references
fn main(){
let mut s = String::from("hello");
modify(&mut s);
println!("{}", s);
}
fn modify(s: &mut String){
s.push_str(" world");
}
Multiple Mutable References
Can we have multiple mutable references to the same variable?
fn main() {
let mut 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.
fn main() {
let mut s = String::from("hello");
{
let s1 = &mut s;
s1.push_str(" world");
} //s1 goes out of scope here
let 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.
fn main() {
let mut s = String::from("hello");
let s1 = &s; //immutable borrow
let s2 = &s; //immutable borrow
let s3 = &mut s; //mutable borrow
println!("{}, {}, {}", 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.
fn main() {
let mut s = String::from("hello");
let s1 = &s; //immutable borrow
let s2 = &s; //immutable borrow
println!("{} and {}", s1, s2);
let s3 = &mut s; //mutable borrow
println!("{}", 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.
fn main() {
let s = dangle();
}
fn dangle() -> &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.
fn main() {
let s = no_dangle();
println!("{}", s);
}
fn no_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)
You can find me here: Francesco
Top comments (5)
Cool post! 🙌
Just to point out we have a built-in feature that allows folks to create a series on DEV. If you're interested, here's an article that explains how:
Best Practices for Writing on DEV: Creating a Series
Sloan the DEV Moderator for The DEV Team ・ Apr 17 '23
And speaking of series, you might enjoy checking out the series that this article is a part of... it's called Best Practices for Writing on DEV and has lots of great guidance for writing on DEV.
Hope this info is helpful and thanks for sharing this post!
It’s great to see more and more Rust content. Thanks for all your work on these!
you are welcome
I'm loving this Rust series!
Thanks Sabrina!