我正在练习我在阅读 The Book 时学到的 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
}
}
Run Code Online (Sandbox Code Playgroud)
我认为您的心智模型的关键问题是您只是将其Box<T>视为一个指针。Rust 引用(以及大多数智能指针,如Box<T>)不仅仅是指针,而是有效的指针。也就是说,没有空引用,并且引用必须始终指向有效数据。
当我们尝试做 时self.list = **bx;,我们将数据从 移动bx到self.list。但是,bx不拥有其数据。当可变借用bx结束时,实际所有者将持有无效数据。
那么我们该怎么办?最简单的方法是有时称为琼斯的技巧,我们将数据切换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
}
}
Run Code Online (Sandbox Code Playgroud)
为了稍微更惯用一点&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
}
}
}
Run Code Online (Sandbox Code Playgroud)
结果是现在您几乎可以毫不费力地使列表通用。
如果你想更多地了解 Rust 的所有权模型如何影响链表的实现,你可以做的没有比优秀系列Learn Rust With Entirely Too Many Linked Lists更好的了。该系列详细介绍了此处的所有内容以及许多变体。