Reputation: 3424
I try to understand the borrow mechanism and references in Rust, and for this reason I created the following small example:
extern crate core;
use core::fmt::Debug;
#[derive(Copy, Clone)]
pub struct Element(pub (crate) [u8; 5]);
impl Debug for Element {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "Element({:?})", &self.0[..])
}
}
impl Element {
fn one() -> Element {
Element([1, 1, 1, 1, 1])
}
fn double(&self) -> Element {
let mut result = *self;
for i in 0..5 { result.0[i] = 2*result.0[i]; }
result
}
fn mut_double(&mut self) -> Element {
for i in 0..5 { self.0[i] = 2*self.0[i]; }
*self
}
}
fn main() {
let mut a = Element::one();
println!("a = {:?}", a); // a = Element([1, 1, 1, 1, 1])
a = a.double();
println!("a = {:?}", a); // a = Element([2, 2, 2, 2, 2])
a = (&a).double();
println!("a = {:?}", a); // a = Element([4, 4, 4, 4, 4])
a = a.mut_double();
println!("a = {:?}", a); // a = Element([8, 8, 8, 8, 8])
a = (&mut a).mut_double();
println!("a = {:?}", a); // a = Element([16, 16, 16, 16, 16])
}
So, the above code works, but my confusion comes when calling the double
method. As you can see it is defined as fn double(&self) -> Element
, so it basically takes an immutable reference. Now in the main, I create a new Element
variable called a
, and then call double
method on it twice. First time I just do a.double()
, second time (&a).double()
. Both of them seem to work correctly, but I do not understand why the first call a.double()
is a valid and compiler doesn't complain about it. Since a
is of type Element
, not of type &Element
, and clearly the double
method asks for &Element
, so about a reference. Same thing also happens with the mut_double
method. Why do I not have to specify (&a)
or (&mut a)
when calling the double
and mut_double
methods, respectively? What is happening under the hood with Rust?
Upvotes: 2
Views: 217
Reputation: 30001
Short: the language works like that because it's a lot more convenient.
Long (extract from the book, emphasis is mine):
Where’s the
->
Operator?In languages like C++, two different operators are used for calling methods: you use
.
if you’re calling a method on the object directly and->
if you’re calling the method on a pointer to the object and need to dereference the pointer first. In other words, ifobject
is a pointer,object->something()
is similar to(*object).something()
.Rust doesn’t have an equivalent to the
->
operator; instead, Rust has a feature called automatic referencing and dereferencing. Calling methods is one of the few places in Rust that has this behavior.Here’s how it works: when you call a method with
object.something()
, Rust automatically adds in&
,&mut
, or*
soobject
matches the signature of the method. In other words, the following are the same:p1.distance(&p2); (&p1).distance(&p2);
The first one looks much cleaner. This automatic referencing behavior works because methods have a clear receiver—the type of
self
. Given the receiver and name of a method, Rust can figure out definitively whether the method is reading (&self
), mutating (&mut self
), or consuming (self
). The fact that Rust makes borrowing implicit for method receivers is a big part of making ownership ergonomic in practice.
Upvotes: 4