理解 &mut &mut 引用的问题

13

我正在学习this教程,但是我不太理解以下代码:

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

其中mail_account是一个结构体,.serialize()是从Borsh Serialize派生的方法。

另外需要注意的一点是account是来自solana_program创建的AccountInfo结构体,data的类型为:Rc<RefCell<&'a mut [u8]>>

作者解释代码如下:

你可能会注意到巧妙的&mut &mut account.data.borrow_mut()[..]表达式。 serialize()方法将可变u8切片的引用作为参数,borrow_mut()方法返回RefMut。我们无法将RefMut传递给期望切片的方法,因此我们获取RefMut的可变切片,该方法返回u8的可变切片

我理解的是,我们想将当前的mail_account结构体写入account.data,这就是为什么我们借用了对account.data的可变引用。但我不明白为什么重要添加[..]?我认为这与serialize()期望一个切片有关。

我也很难理解为什么在account.data.borrow_mut()[..]中添加&mut &mut会创建一个u8切片。这是如何转换为u8的?对我来说,它似乎只是向原始引用添加了额外的可变引用。(类型是如何从RefMut变为&[u32]的?)


2
相信 &mut *account.data.borrow_mut() 可以工作。 - Ibraheem Ahmed
@IbraheemAhmed,你能解释一下你的逻辑吗? - Craig
2个回答

7

所以,我猜你有多个问题:

  1. 我不明白的是为什么要添加[..]?

  2. 我也不太理解如何通过在account.data.borrow_mut()[..]中添加&mut &mut来创建u8切片。这是如何转换为u8的?

这些问题有点相互交织,正如在第一个问题的答案中所看到的。

问题1

当我们查看关于某些索引情况的文档时,我们可以看到,

account.data.borrow_mut()[..]

is sugar for

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

为什么这是一个有效的表达式?

..RangeFull 的缩写。

RangeFull 对于 SliceIndex<[u8]> 有一个实现。

通过这个 全局实现,我们得到了一个 IndexMut<RangeFull> for [u8],提供了

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

现在,其他答案和评论中提到的解引用强制转换和/或自动解引用开始生效。
account.data.borrow_mut().index_mut(..)

RefMut<&mut [u8]> 实现了 DerefMut,其中 Deref<Target = &mut [u8]> 是其超级特质。

&mut [u8] 通过 DerefMut 实现,其超级特质为 Deref<Target = [u8]>

参考文献 中所述,编译器将对接收表达式进行重复解引用,以获取候选类型列表。它还会为每个从解引用中得出的类型添加引用类型和可变引用类型,并将其添加到候选类型列表中。从这些候选类型中选择一个提供要调用的方法。

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

(在第7个例子中,我们对指针类型 &mut [u8] 进行了解引用操作,因此没有使用 DerefMut Trait。)

这个列表中唯一一个提供 index_mut() 方法的类型是 &mut [u8],通过 IndexMut<FullRange> 实现为 [u8],因此选择 &mut [u8] 作为接收器类型。 index_mut() 的返回类型也是 &mut [u8]

所以现在,我们可以理解,*(account.data.borrow_mut().index_mut(..)) 的类型是 [u8]

因此:

问题2

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

类型为&mut &mut [u8]

补充

&mut [u8]具有Write实现,因此需要&mut &mut [u8]

并且序列化

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

需要使用类型为&mut W的参数,其中W实现了Write

对于实现Write的类型W的值的引用需要是可变的,因为我们想要跟踪类型为W的值中的实际写入位置。对于&mut [u8]的情况,我们只需更改引用以从基础切片的不同位置开始,因此我们需要一个可变引用来更改可变引用本身,而不仅仅是底层数据。

补充2

只需使用

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

在第4部分的自动解引用部分,*account...不是先解引用,然后我们再使用...().deref_mut()进行第二次解引用吗?为什么不只需要一次解引用就可以从RefMut <&mut [u8]>转换为&mut [u8]?我尝试打印account.data.borrow_mut().deref_mut()*account.data.borrow_mut().deref_mut()的类型以理解,但对于这两个表达式都遇到了RefMut<'_, &mut [u8]>中找不到deref_mut()的问题。 - Craig
哎呀,我没有使用std::ops::DerefMut,并且了解到deref_mut()实际上只是返回另一个引用。现在我得到了account.data.borrow_mut().deref_mut()&mut &mut [u8],我理解它是对切片的可变引用的可变引用。当我尝试*account.data.borrow_mut().deref_mut()时,我遇到了所有权错误:Cannot move out of a mutable reference. move occurs because value has type &mut [u8] - Craig
1
正如您在参考文献中所看到的,对于非指针类型,*v只是*std::ops::DerefMut::deref_mut(&mut v)。因此,解引用意味着使用deref_mut,但仍然将解引用运算符*应用于结果。而RefMut<_>则是一个非指针类型。 - typetetris
1
t = *something;对于类型为&mut Tsomething意味着我们尝试获取由something引用的值的所有权(而不仅仅是借用)。 (除非T实现了Copy。)您可以使用*something来分配值并调用方法或者可能还有其他操作,但是您无法将其移出该值。 - typetetris

1
这种魔法叫做类型强制转换。你可以在这里这里阅读相关内容。基本上,Rc<RefCell<&'a mut [u8]>>会被隐式转换为&mut [u8]。Rust的实现方式如下:
  1. 使用.运算符时自动取消引用 - 当您调用.borrow_mut()时,Rc<RefCell<...>会被取消引用为RefCell<>
  2. borrow_mut返回类型为RefMut<T>的值,该类型实现了特质DerefMut<T>
  3. 方法.serialize&mut [u8]作为参数,因此您不能仅使用&mut data.borrow_mut()调用它,因为它具有&mut RefMut<&mut [u8]>类型。
  4. 在RefMut之前添加额外的&mut允许Rust编译器使用类型强制转换。看到类型&mut &mut RefMut<&mut [u8]>时,它可以自动将内部的&mut RefMut<T>转换为&mut T,因此您得到&mut &mut &mut [u8]。然后它可以重复这个过程两次,每次将&mut &mut转换为&mut。因此最终您将得到&mut [u8]
更明确的做法是解引用RefMut
&mut *account.data.borrow_mut()

它做的是同样的事情,但直接执行。

1
不,这里没有发生解引用强制转换。这是作为方法查找的自动解引用,它是一种独特的机制。 - Sven Marnach
3
.serialize() 方法不接受 &mut [u8],而是接受一个实现了 Write 特质的 &mut W。该特质已经被 &mut [u8] 实现,因此要传入 &mut W,您实际上需要 &mut &mut [u8]。(实际上,serialize() 方法应该直接接受一个 W)。 - Sven Marnach
额外的 &mut 与任何类型强制转换完全无关,最重要的是这里没有发生任何类型强制转换。 - Sven Marnach
@SvenMarnach 感谢您指出自动解引用的问题,这样我就可以重新调整我的代码了。 - typetetris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接