为何&mut Box<T>无法同时多次可变借用,而&mut T可以?
为什么
&mut Box<List<T>>无法多次可变借用,而&mut List<T>可以? 要搞清楚这个差异,咱们得从Rust的借用规则和参数类型的本质两个方面入手:
1. 先明确两个参数的本质
bar函数的参数是&mut List<T>:这是直接对List<T>实例的可变引用,它指向的是链表节点本身(虽然Cons里的tail是堆分配的Box,但这个引用直接指向节点数据)。foo函数的参数是&mut Box<List<T>>:这是对Box<List<T>>的可变引用,Box本身是一个拥有所有权的堆指针,所以这个引用指向的是“指针”本身,而非链表节点数据——要拿到节点数据,需要两次解引用(**list)。
2. 可变借用的核心规则
Rust的借用检查器有个核心原则:同一时间,一个内存区域只能有一个可变引用,或者多个不可变引用。但有个关键例外:如果可变引用指向的是不重叠的内存区域,那么可以同时存在多个可变引用。
3. 分析bar函数的情况
当你在bar里写match *list时,*list是对&mut List<T>的解引用,得到List<T>的可变借用视图。如果匹配到List::Cons(ref mut head, ref mut tail):
ref mut head是对Cons变体中第一个字段(T类型)的可变引用;ref mut tail是对Cons变体中第二个字段(Box<List<T>>类型)的可变引用。
这两个引用指向的是List<T>实例中完全不重叠的两个内存字段,所以借用检查器允许同时存在这两个可变引用——这就是&mut List<T>能进行多次可变借用的原因。
4. 分析foo函数的情况
当你在foo里写match **list时,需要两步解引用:
- 第一次解引用
*list:得到Box<List<T>>的可变引用; - 第二次解引用
**list:得到Box内部List<T>的可变引用。
此时如果尝试创建多个可变借用(比如同时使用**list的子字段借用,以及再次借用list本身),就会触发冲突:
- 比如你在
match块里尝试修改整个Box的内容(*list = Box::new(List::Nil)),这会要求对&mut Box<List<T>>进行可变引用; - 而此时
head和tail已经是**list的可变引用,这两个引用的内存区域和&mut Box<List<T>>的操作是重叠的——修改Box的内容会直接替换掉**list指向的节点,导致head和tail的引用失效,所以Rust会阻止这种操作。
举个具体的错误例子
比如在foo函数中尝试修改Box的内容,就会触发编译错误:
enum List<T> { Nil, Cons(T, Box<List<T>>) } fn foo<T>(list: &mut Box<List<T>>) { match **list { List::Nil => return, List::Cons(ref mut head, ref mut tail) => { // 编译错误:无法同时借用`*list`为可变引用 *list = Box::new(List::Nil); } } }
而在bar函数中,同时操作子字段的借用完全没问题:
fn bar<T>(list: &mut List<T>) { match *list { List::Nil => return, List::Cons(ref mut head, ref mut tail) => { // 可以同时修改head和tail,因为它们指向不重叠的字段 *head = Default::default(); *tail = Box::new(List::Nil); } } }
总结一下:&mut List<T>允许拆分多个可变借用,是因为它直接指向链表节点,节点的子字段是不重叠的内存区域;&mut Box<List<T>>的问题在于它指向的是堆指针本身,对指针的可变引用和对其内部数据的可变引用是重叠的,所以无法同时存在多个可变借用。
内容的提问来源于stack exchange,提问作者Andrew Sun
相关产品推荐
相关产品推荐

