A reference conception problem

For the below explanation, let me introduce another variable to give the reference a name.

struct G { n: i32 }
let g: G = G { n: 10 };
let r: &G = &g;
let d: i32 = r.n;
println!("d is {d}");

Where other languages such as C require you to dereference a pointer in order to access fields, as in d = (*r).n;, and even introduce a new operator to make this operation more readable: d = r->n, in Rust, a different approach is taken. You can still be explicit and use

let d: i32 = (*r).n;

(Here, r is &G, and dereferencing gives *r: G, of which then the field n can be accessed.)

But where C requires you to write r->n, Rust chose to let you re-use the r.n notation. This is made possible by rules to automatically introduce dereferencing operators for field access operations as well as for method calls, as necessary, in order for the field or method to successfully be found. Of course this is a trade-off, the downside is that the operation becomes less explicit, and also that cases where multiple types —say, G and &G– would both have a method of the same name become ambiguous in principle, and resolved by some slightly nontrivial order of priority in practice, (although the actually complicated full set of rules really only applies for methods, since the types &T and &mut T which will be considered for method calls don’t have any fields).

In practice, IMO the benefits of the convenience are probably worth it; the “ambiguity”-related problems mentioned above come up occasionally, but not too often —e.g. this recent thread is a problem encountered because of it— and the downside of code being less explicit, in particular the fact that a dereferencing operation might be hidden is a lot more reasonable in Rust than in, say, C because of Rust’s memory safety, so you don’t need to be careful about and review every single instance of dereferencing in Rust. And in unsafe Rust, where you can dereference raw pointers (like you’d do in C all the time), this convenience feature goes away for those raw pointers, any you’d need to explicitly write something like (*p).n again, if p: *const G is a raw pointer, so the dereferencing stays explicit and easy to spot in code review.

3 Likes