我正在练习阅读《书》时学到的Rust概念。我已经能够List
通过复制Box
并将其分配list
给复制的框来遍历我的枚举,但是凭直觉,我觉得必须有一种方法只是“使其指向行中的下一个指针”。
如果我尝试不使用bx.clone()
,就像这样:self.list = **bx
,我将得到“无法从**bx
可变引用后面移出”。这意味着我需要拥有它,但是我不能拥有bx
它,因为在取消引用它时需要将其作为参考进行移动if let
。
是否可以或建议在不复制参考的情况下移动参考?
#[derive(Clone)]
enum List {
Cons(u32, Box<List>),
Nil,
}
struct ListHolder {
list: List,
}
impl Iterator for ListHolder {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if let Cons(num, bx) = &mut self.list {
let val = *num;
self.list = *bx.clone(); // This is the key line
Some(val)
} else {
None
}
}
}
use List::*;
fn main() {
let list_inst = ListHolder {
list: Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))),
};
for i in list_inst.into_iter() {
println!("{}", i); // Prints 1, 2, 3 as expected
}
}
我认为您的心理模型的关键问题是您只是将其Box<T>
视为指针。Rust引用(以及大多数智能指针,如Box<T>
)不仅是指针,而且是有效的指针。也就是说,没有空引用,并且引用必须始终始终指向有效数据。
当我们尝试这样做时self.list = **bx;
,我们会将数据从bx
移到self.list
。但是,bx
并不拥有其数据。当可变借贷bx
结束时,实际所有者将持有无效数据。
那么我们该怎么办?最简单的方法是有时称为Jones'Trick的方法,在该方法中,我们将数据切换出bx
一些虚拟值。现在,数据中的实际所有者bx
将不会保存无效数据。那么我们该怎么做呢?这是该函数的功能范围,该函数std::mem::replace
接受一个可变引用和一个值,并用该值替换该可变引用后面的数据,并返回该可变引用后面的内容(包括所有权!)。这正是我们想要在此处执行的操作self.list = std::mem::replace(&mut **bx, List::Nil)
。再次,List::Nil
只是一些伪数据;任何List
都将完全相同。
enum List {
Cons(u32, Box<List>),
Nil,
}
struct ListHolder {
list: List,
}
impl Iterator for ListHolder {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if let Cons(num, bx) = &mut self.list {
let val = *num;
self.list = std::mem::replace(&mut **bx, List::Nil); // This is the key line
Some(val)
} else {
None
}
}
}
use List::*;
fn main() {
let list_inst = ListHolder {
list: Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))),
};
for i in list_inst.into_iter() {
println!("{}", i); // Prints 1, 2, 3 as expected
}
}
为了更加惯用,&mut **bx
我们可以简单地使用bx.as_mut()
来从框中获取可变的引用,而不是。另外,由于已经实现,因此不需要进行into_iter
调用,因此不需要将其转换为一个。你也可以想知道和,为什么我们仍然必须为一个临时变量。list_inst
ListHolder
Iterator
num
val
原因是该值仍只是参考,我们没有所有者(self.list
)的所有权。这意味着我们必须复制它以返回。u32
实现,Copy
所以这并不是一个真正的问题,但是,如果您尝试使链表在其元素的类型中具有通用性,则根本行不通。let val = *num;
与我们以前无法做到的“移出借用内容”相同。
解决方案是std::mem::replace
不仅要获取背后数据的所有权bx
,还要获得整个列表的所有权。因此,如果我们std::mem::replace(&mut self.list, List::Nil)
在销毁之前使用,self.list
将被替换为虚拟值,并且我们将拥有实际列表的所有权,包括值和列表尾部。这也意味着我们可以拥有self.list = *bx
,就像我确定您最初想要的那样。
impl Iterator for ListHolder {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if let Cons(num, bx) = std::mem::replace(&mut self.list, List::Nil) {
self.list = *bx;
Some(num)
} else {
None
}
}
}
现在的结果是,您几乎可以毫不费力地使列表通用。
如果您想了解更多有关Rust的所有权模型如何影响链表实现的信息,您将无法比学习优秀的Rust系列文章中的链表更好。该系列详细介绍了这里的所有内容以及许多变体。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句