Rust: A Generally View of Reference and Its Mutability
Link
0. Overview
Rust's most essential concept is the
owner
. A variable is the unique owner
of some data, which means that when the variable is drop
ped, the data is removed from memory and cannot be recovered. This owner
model provides a way to take responsibility for data in memory, preventing memory leaks and double-free errors.If someone wants to access or modify data but doesn't want to take responsibility for it, they can use a
reference
(&
). In Rust, a reference
is more similar to a pointer
in C/C++ than a reference
in C++.By default, a
reference
is immutable, meaning both the reference
itself and the variable it refers to cannot be changed. However, when talking about a "mutable reference," there is a problem similar to the "top-level/low-level const pointer" in C/C++: which one is mutable, the reference
itself or the variable it refers to?1. The reference
in Rust is the thing known as the pointer
in C/C++
If we use an editor plugin with
rust-analyzer
, code will be displayed as follows:The analyzer shows that the type of
b
is &String
and c
is &&String
. This is similar to the *int
and **int
in C/C++, right?2. Top-Level/Low-Level Mutable Reference in Rust
Let's start with C/C++. As C++ Primer explains, there are two types of
const
in C/C++:The former
b
is a pointer to a const value a
. a
is not declared as a const variable, yet b
treats it as such, disallowing any modifications. However, b
itself is not const; it can be made to point to another const value, e.g.:In this case, we declare
b
to be a low-level constant pointer.By contrast, top-level
const
means that the variable to which c
points may not be const
, but the pointer c
itself cannot be edited. We cannot make c
point to another variable.Therefore, when we consider
references
in C++, they are naturally top-level const
.The "location" it refers to is fixed and assigned when the
reference
is defined. d
is simply an alias of a
, nothing more.Come back to Rust, we can define similar things like the above pointers.
Here,
s3
is a low-level, mutable reference. The last line changes the value of the variable that s3
refers to, namely s1
. After this, the value of s1
is “hello new world”
and s3
still refers to s1
.In this case,
s4
is a top-level mutable reference. The last line changes the “location” that s4
refers to.Originally,
s4
referred to s1
, whose value was "hello world"
. After the last line was executed, s4
now referred to s2
, whose value was "hello another new world"
.3. Automatic Dereferencing / Deref Coercion
Things seem no different from pointers in C/C++, don't they? However, it gets more complicated because they are
references
, which means they must act like aliases.It works. The outputs are the same.
s3
can be treated as an alias of s1
here.But:
If we remove the dereference sign (*), the assignment won't work.
s3
cannot be used as an alias for s1
; it is merely a pointer.And in the top-level case:
We don't use
*
here and this time it works. We DO NOT want the compiler to treat s4
as an alias. Instead, we want it to point to another location, such as from s1
to s2
.Let's take a closer look at how the
reference
behaves, such as if it is an alias or a pointer. Here are the descriptions:- From https://doc.rust-lang.org/std/primitive.reference.html
Clone
(Note that this will not defer toT
’sClone
implementation if it exists!)
The following traits are implemented for all&T
, regardless of the type of its referent:
- From https://doc.rust-lang.org/reference/expressions/field-expr.html#automatic-dereferencing
If the type of the container operand implements Deref or DerefMut depending on whether the operand is mutable, it is automatically dereferenced as many times as necessary to make the field access possible. This processes is also called autoderef for short.
- From https://doc.rust-lang.org/reference/expressions/method-call-expr.html
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 candidateT
, add&T
and&mut T
to the list immediately afterT
.
All primitive references have a trait called
Deref
. Two mechanisms, "automatic dereferencing" and "Deref coercion", can use Deref
to implement "referencing" behavior.Generally, in the following cases, reference
A
acts as an alias:A.attribute_1
A.method_2(B)
B.method_3(A)
function_4(A)
Otherwise,
A
acts as a pointer, including:A = B
if A == B {…}
match A { B => … }
For more information on coercion, check out the following links:
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.