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:

greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?;

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:

  1. Why should we add [..]?

  2. 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:

use borsh::maybestd::io::Write; // Simply add 'borsh = "0.9.1"' to Cargo.toml
use std::cell::RefCell;
use std::rc::Rc;

struct Node<'a> {
    value: Rc<RefCell<&'a mut [u8]>>,
}

fn ref_mut<T: Write>(x: &mut T) -> &mut T {
    x
}

fn main() {
    let mut v: [u8; 3] = [1, 2, 3];
    let node = Node {
        value: Rc::new(RefCell::new(&mut v)),
    };
    println!("{:?}", ref_mut(&mut &mut node.value.borrow_mut()[..]));
}

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

node.value.borrow_mut()[..]

is sugar for

*(node.value.borrow_mut().index_mut(..))

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

fn index_mut(&mut [u8], index: RangeFull) -> &mut [u8]

Why double &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:

println!("{:?}", ref_mut(&mut &mut node.value.borrow_mut()[..]));

In this line, we pass &mut &mut node.value.borrow_mut()[..] as a parameter into the function named ref_mut:

fn ref_mut<T: Write>(x: &mut T) -> &mut T {
    x
}

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:

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

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:

#[stable(feature = "rust1", since = "1.0.0")]
#[doc(notable_trait)]
#[cfg_attr(not(test), rustc_diagnostic_item = "IoWrite")]
pub trait Write {
    // -- snip --
    #[stable(feature = "rust1", since = "1.0.0")]
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    // -- snip --
}

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:

fn ref_mut<'a>(x: &'a mut &'a mut [u8]) -> &'a mut &'a mut [u8] {
    x
}

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() has RefMut<&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.

node.value.borrow_mut().index_mut(..)

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.

  1. RefMut<&mut [u8]> using node.value.borrow_mut()

  2. &RefMut<&mut [u8]>

  3. &mut RefMut<&mut [u8]>

  4. &mut [u8] using *node.value.borrow_mut().deref_mut() (means dereferenced from RefMut<&mut [u8]>)

  5. &&mut [u8]

  6. &mut &mut [u8]

  7. [u8] using *(*node.value.borrow_mut().deref_mut()) (now dereferenced from &mut [u8])

  8. &[u8]

  9. &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:

println!("{:?}", ref_mut(&mut *node.value.borrow_mut()));

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.

  1. A Rust & type is stored as a pointer, sometimes with a length and other information (see below responses).

  2. When you call foo.bar(), or access foo.bar, rust will automatically dereference foo if it has a type of &Foo.

  3. There is a trait called Deref that some smart pointer types implement, to change how the * operator works.

Reference

Last updated