References in Rust are interesting. It is clear that there are two types of references: immutable and mutable.
Immutable references allow reading a resource but the resource cannot be modified. There can be more than one immutable reference in scope for a given resource at the same time.
let hello = String::from("hello world");
let a = &hello;
let b = &hello; //valid second immutable reference because a is also immutable
fn get_string_length(s: &String) -> usize {
s.len()
}
get_string_length(&a); //borrows ownership from hello
fn change_string(s: &mut String) {
s.insert_str(0, "I say ");
}
// change_string(&b); compiler error: we provided an immutable reference
Mutable references can modify their resource. There can be at most one mutable reference to a given resource in scope at the same time. Further, there cannot be a mutable borrow when there is also an existing immutable borrow in scope for the same resource.
let mut world = String::From("world");
let a = &mut world;
//let b = &mut world; //compiler error: an existing mutable reference already exists
//let c = &world; //compiler error: cannot borrow immutable because we already borrowed as mutable
fn prepend_hello(s: &mut String) {
s.insert_str(0, "Hello ");
}
prepend_hello(a);
Immutable and mutable references use ‘&’ when we want to define them. Where then does the ref
keyword fit in?
It wasn’t clear to me so I decided to dig into it a bit more. The first resource I found http://xion.io/post/code/rust-patterns-ref.html, detailed the differences by using pattern matching examples. When trying to match against a reference type, the &
is important.
let foo = &42;
match foo {
&42 => println!("Matched"),
_ => println!("Not matched"),
}
The match statement here is matching against references, specifically against a reference to 42. On the other hand, ref
is used when we want the match destructuring result to be a reference but we don’t necessarily want to match against a reference.
let foo = 42;
match foo {
ref v => println!("v is a reference to {:?}", v), //v is a &integer
}
This is useful for avoiding issues where things are moved and ownership changes.
let v : Vec<(String, String)> = vec![String::from("first"), String::from("second")];
let (s, t) = v[0]; //compiler error: attempt to move out of index of v
println!("{}, {}", s, t);
In this example on the second line, the values from v[0]
are moved into s
and t
respectively, which fails because copy is not implemented for String.
We can get around this by changing the second line to one of the following.
let (s,t) = &v[0];
let (ref s, ref t) = &v[0];
I think the second way is preferred but I don’t know if there is a practical difference between the two.