The easy way of answering this is to just define, in each grammatical context, whether the name of a reference means the reference itself or the referenced value. So if x
is a reference, and a reference in a simple name expression means a reference, then x + 7
gives a type error; inconvenient, but easy for the compiler. Then it's the user's responsibility to write *x + 7
when they want the referenced value. However, even strongly-typed languages like Rust usually aren't this pedantic about references.
The more ergonomic way of doing it is by implicit coercions, using information about static types. Let's use a more familiar example from Java:
int x = 5;
System.out.println(x + 2.5);
Here the variable x
is an integer, but its value must be a double
for the expression x + 2.5
to make sense. The compiler knows (based on the static types of the expressions) that the expression x
is expected to produce a double
but it is actually of type int
, and so it inserts a coercion from int
to double
.
Likewise, consider the following Rust code:
let x_val = 5;
let x_ref = &x_val;
print!("{}", x_ref + 7);
In this code, x_ref
has type &i32
, a reference to an integer. But in the expression x_ref + 7
, an i32
is expected. The Rust compiler knows both of these facts, because of the static type of x_ref
and the static type expected by the +
operator (given that its other operand is an integer), so it inserts a coercion from &i32
to i32
by dereferencing.