Note that there are some more situations where (de)reference happens automatically.
For example, &&&&&&&&T
gets automatically coerced to &T
where necessary:
fn print_i32_ref(val: &i32) {
println!("The value is {} and {} and {}", val, *val, &&&&val);
}
fn inc_i32_ref(val: &mut i32) {
*val += 1;
println!("The value is {} and {} and {}", val, *val, &&&&val);
}
fn main() {
let mut x = 5;
print_i32_ref(&x);
print_i32_ref(&&&x); // this works
//print_i32_ref(x); // but this doesn't
inc_i32_ref(&mut x);
inc_i32_ref(&mut &mut &mut x); // this works too
//print_i32_ref(x); // but this doesn't
}
(Playground)
Note that you can also see in this example (if you comment-in the commented-out lines), that function arguments which take a reference require at least one &
to turn the value into a reference.
Another situation where &
and *
can be ommitted is the receiver for method calls (i.e. the value that you write left of the ".
"):
fn main() {
let v = vec![10, 20, 30];
let l1 = (&v).len(); // we could write this
let l2 = v.len(); // but we normally just write this, as the `&` is added automatically
let r = &v;
let l3 = (*r).len(); // we could write this (as the `&` is added automatically)
let l4 = r.len(); // but we normally just write this
let l5 = (&r).len(); // and we can also still write this
let b = Box::new(r);
let l6 = (**b).len(); // we could write this (as the `&` is added automatically)
let l7 = (*b).len(); // we also could write this
let l8 = b.len(); // but we even can write this, because the `*` to dereference the box is also added automatically
let l9 = (&b).len(); // and, of course, we could also write this
assert_eq!(l1, 3);
assert_eq!(l2, 3);
assert_eq!(l3, 3);
assert_eq!(l4, 3);
assert_eq!(l5, 3);
assert_eq!(l6, 3);
assert_eq!(l7, 3);
assert_eq!(l8, 3);
assert_eq!(l9, 3);
}
(Playground)
The exact rules are explained here:
[…]
When looking up a method call, the receiver may be automatically dereferenced or borrowed in order to call a method. This requires a more complex lookup process than for other functions, since there may be a number of possible methods to call. The following procedure is used:
The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression's type, adding each type encountered to the list, then finally attempting an unsized coercion at the end, and adding the result type if that is successful. Then, for each candidate T
, add &T
and &mut T
to the list immediately after T
.
This confused me when I was beginning to learn Rust, because it felt like I don't need &
or *
.
But this isn't generally true (in particular not for method/function arguments), as you can see in the first example that the following code would fail:
print_i32_ref(&x);
print_i32_ref(&&&x); // this works
- //print_i32_ref(x); // but this doesn't
+ print_i32_ref(x); // but this doesn't
(Playground)
Errors:
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:14:19
|
14 | print_i32_ref(x); // but this doesn't
| ------------- ^
| | |
| | expected `&i32`, found `i32`
| | help: consider borrowing here: `&x`
| arguments to this function are incorrect
|
note: function defined here
--> src/main.rs:1:4
|
1 | fn print_i32_ref(val: &i32) {
| ^^^^^^^^^^^^^ ---------
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error
So Rust indeed does make a difference between x
and &x
, but there are some places where this conversion happens magically. This can be a source of confusion.