Understanding &mut &mut Reference
&mut? &mut &mut?? &mut &mut &mut??? &mut &mut &mut &mut...
Problem Statement
I followed a Solana tutorial and had some trouble understanding the following code:
where greeting_account
is a struct and .serialize()
is a method derived from BorshSerialize.
One other thing to note is that account
is an AccountInfo struct from the solana_program crate and data
has to type: Rc<RefCell<&'a mut [u8]>>
The magic with double &mut
and [..]
make me, a Rust newbie, really feel scared.
This problem has troubled me for a long time, making me care neither for food nor drink, and now I want to beat fear and try to understand this sentence.
Resolution
First of all, let me google it, and fortunately, I found the most relevant answer just on the top of the result: Trouble understanding &mut &mut reference.
Let me split the problem into the following:
Why should we add
[..]
?Why double
&mut
?
The code in the tutorial is very long and deep, if you don't want to clone the code, the following code (which has the same effect as the code in the tutorial I think...) is simplified for you to understand:
where value
in the struct Node
has the same type as the data
in account
, and the function ref_mut
has the same receiver as the function serialize
.
Why should we add [..]
?
[..]
?Most of this part is referenced from the top answer in Trouble understanding &mut &mut reference.
If we want to know the secret of the double &mut
, we may go through the [..]
first.
When we look at the documentation about certain cases of indexing, we see, that
is sugar for
Why is that a valid expression?
..
is a shorthand for RangeFull
.
RangeFull
has an implementation for SliceIndex<[u8]>
.
With this blanket implementation, we get a IndexMut<RangeFull> for [u8]
, which provides
Why double &mut
?
&mut
?The double &mut
really bothered me for a while, since the common case may only have one in it.
After I made it clear, I found it is actually a simple problem of type. Let's go back to the code above again, and locate the line of code which we have a problem with:
In this line, we pass &mut &mut node.value.borrow_mut()[..]
as a parameter into the function named ref_mut
:
As Trait Bound Syntax says:
The
impl Trait
syntax works for straightforward cases but is actually syntax sugar for a longer form, which is called a trait bound; it looks like this:This longer form is equivalent to the example in the previous section but is more verbose. We place trait bounds with the declaration of the generic type parameter after a colon and inside angle brackets.
so the type T
we input to ref_mut
should implement Write
, which is the same as the Write
in the problem, since the definition of the trait Write is very long, so let's go to the definition of the main method (write
for a writer) in it:
According to the method, the instance of Write
should be a mutable reference of an array with u8
type.
Now let's push it backwords, in order to satisfy the bound Write
, the generic type in ref_mut
has to be &mut [u8]
, and we remove the generic type in ref_mut
temporarily, which simply replaces T with &mut [u8]
, besides, add some suitable lifetime parameters:
From now on, the problem is simple for everyone, we have a function that receives one parameter with the type: &mut &mut [u8]
.
Here are the types of every part of &mut &mut node.value.borrow_mut()[..]
:
node.value.borrow_mut()
hasRefMut<&mut [u8]>
node.value.borrow_mut()[..]
has[u8]
&mut node.value.borrow_mut()[..]
has&mut [u8]
&mut &mut node.value.borrow_mut()[..]
has&mut &mut [u8]
To keep track of the actual writing position, we need a mutable reference to the mutable reference, and the input should have double &mut
now.
Now the auto dereferencing kicks in.
And RefMut<&mut [u8]>
implements DerefMut
which have Deref<Target = &mut [u8]>
as a super trait.
And &mut [u8]
implements DerefMut
with Deref<Target = [u8]>
as a super trait.
As mentioned in the reference, the compiler will now take the receiver expression and dereference it repeatedly, so it gets a list of candidate types. It also adds for each type resulting from a dereference of the reference type and the mutable reference type to the list of candidate types. From these candidate types, it selects one providing the method to call.
RefMut<&mut [u8]>
usingnode.value.borrow_mut()
&RefMut<&mut [u8]>
&mut RefMut<&mut [u8]>
&mut [u8]
using*node.value.borrow_mut().deref_mut()
(means dereferenced fromRefMut<&mut [u8]>
)&&mut [u8]
&mut &mut [u8]
[u8]
using*(*node.value.borrow_mut().deref_mut())
(now dereferenced from&mut [u8]
)&[u8]
&mut [u8]
(In 7. we are dereferencing a pointer type &mut [u8]
so no DerefMut
the Trait is used.)
The first (and only) type in this list provides an index_mut()
method is &mut [u8]
, via the IndexMut<FullRange>
implementation for [u8]
, so &mut [u8]
is selected as receiver type. The return type of index_mut()
is &mut [u8]
as well.
So now, we hopefully understand, the type of *(node.value.borrow_mut().index_mut(..))
is [u8]
.
Thanks to the discussion of dereferencing, node.value.borrow_mut()
can also be dereferenced with *
. Since node.value
has type RefMut<&mut [u8]>
, it will be &mut [u8]
after dereferencing, which can get the actual value simply. Finally after adding one more &mut
, we can also match the type the function needed:
Conclusion
It may be confused if you are familiar with the *
and &
operator in C++ when you just step into the reference in Rust. Since they are two different things, we should avoid using C++ references to think about Rust references.
According to the question Why do references need to be explicitly dereferenced, these words reminded me:
So I’d treat
&
types in Rust like you would*
types in C/C++, while keeping in mind the following points.
A Rust
&
type is stored as a pointer, sometimes with a length and other information (see below responses).When you call
foo.bar()
, or accessfoo.bar
, rust will automatically dereference foo if it has a type of&Foo
.There is a trait called
Deref
that some smart pointer types implement, to change how the * operator works.
Reference
Last updated