Trouble understanding &mut &mut reference

Viewed 1609

I'm following this tutorial and I'm having trouble understanding the following code:

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

where mail_account is a struct and .serialize() is a method derived from Borsh Serialize.

One other thing to note is that account is an AccountInfo struct from the solana_program crate and data has type: Rc<RefCell<&'a mut [u8]>>

The author explains the code as follows:

You might notice the tricky &mut &mut account.data.borrow_mut()[..] expression. The serialize() method takes a reference to a mutable slice of u8 as an argument, the borrow_mut() method returns a RefMut. We can't pass RefMut to a method that expects a slice, so we take a mutable slice of RefMut which returns a mutable slice of u8

What I do understand is that we want to write the current mail_account struct into account.data, which is why we are borrowing a mutable reference to account.data. What I don't understand is why is it important to add the [..]? I assume it has something to do with the serialize() expecting a slice.

I'm also having trouble understanding how adding &mut &mut to account.data.borrow_mut()[..] creates a slice of u8. How is this being converted to u8? To me it seems like it would just be adding additional mutable references to the original one. (How is the type changing from RefMut to &[u32]?)

2 Answers

So, I guess, you have multiple questions:

  1. What I don't understand is why is it important to add the [..]?

  2. I'm also having trouble understanding how adding &mut &mut to account.data.borrow_mut()[..] creates a slice of u8. How is this being converted to u8?

Those questions are kinda intertwined, as you can see inthe anwser to question 1.

Question 1

When we look at the documentation about certain cases of indexing, we see, that

account.data.borrow_mut()[..]

is sugar for

*(account.data.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]

Now the deref coercion and or the auto dereferencing mentioned in other answers and comments kicks in.

account.data.borrow_mut().index_mut(..)

And RefMut<&mut [u8]> implements DerefMut which has Deref<Target = &mut [u8]> as a super trait.

And &mut [u8] implements DerefMut with Deref<Target = [u8]> as 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 the reference type and the mutable reference type to the list of candidate types. From this candidate types it selects one providing the method to call.

  1. RefMut<&mut [u8]> using account.data.borrow_mut()
  2. &RefMut<&mut [u8]>
  3. &mut RefMut<&mut [u8]>
  4. &mut [u8] using *account.data.borrow_mut().deref_mut()
  5. &&mut [u8]
  6. &mut &mut [u8]
  7. [u8] using *(*account.data.borrow_mut().deref_mut())
  8. &[u8]
  9. &mut [u8]

(In 7. we are dereferencing a pointer type &mut [u8] so no DerefMut Trait is used.)

The first (and only) type in this list providing 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

*(account.data.borrow_mut().index_mut(..)) is [u8].

Hence:

Question 2

&mut &mut account.data.borrow_mut()[..]

has type &mut &mut [u8].

Addendum

&mut &mut [u8] is needed as &mut [u8] has a Write implementation.

And serialize

pub fn serialize<W: Write>(&self, writer: &mut W) -> Result<()>

needs an argument of type &mut W, where W implements Write.

The reference to a value of type W implementing Write needs to mutable, as we want to keep track of the actual writing position in the value of type W. In the case of &mut [u8] we simply alter the reference to start at a different position in the underlying slice, so we need a mutable reference to the mutable reference, as we want to alter the mutable reference itself and not only the underlying data.

Addendum 2

Just use

mail_account.serialize(&mut *account.data.borrow_mut())?;

This magic is called type coercion. You can read about it here or here. Basically what happens is an implicit conversion of Rc<RefCell<&'a mut [u8]>> into &mut [u8]. The way Rust does it is as following:

  1. Auto-dereference when use . operator - Rc<RefCell<...> is dereferenced into RefCell<> when you call .borrow_mut().
  2. borrow_mut returns a value of type RefMut<T>, which implements trait DerefMut<T>.
  3. The method .serialize takes &mut [u8] as argument, so you cannot call it with just &mut data.borrow_mut() as it has the type of &mut RefMut<&mut [u8]>.
  4. Adding additional &mut before RefMut allows Rust compiler to use the type coercion. Seeing the type &mut &mut RefMut<&mut [u8]> it can automatically convert inner &mut RefMut<T> into just &mut T, so you get &mut &mut &mut [u8]. Then it can repeat the process twice, each time turning &mut &mut into &mut. So in the end you get &mut [u8].

The more explicit way to do the same thing is to derefrence RefMut:

&mut *account.data.borrow_mut()

It does the same thing but directly.

Related