[英]How can I change a reference to an owned value without clone?
I'm practicing Rust concepts that I've learned while reading The Book.我正在练习阅读本书时学到的 Rust 概念。 I've been able to iterate over my List
enum by copying the Box
and assigning the list
to the copied box, but intuitively, I feel like there must be a way to just "make it point to the next pointer in line".我已经能够通过复制Box
并将list
分配给复制的框来迭代我的List
枚举,但直观地说,我觉得必须有一种方法来“让它指向行中的下一个指针”。
If I attempt to do this without bx.clone()
, like so: self.list = **bx
, I get "cannot move out of **bx
which is behind a mutable reference."如果我尝试在没有bx.clone()
的情况下执行此操作,例如: self.list = **bx
,我会得到“无法移出位于可变引用后面的**bx
”。 Which means that I need it to be owned, but I can't get an owned bx
because I need to move it as a reference when I dereference it in the if let
.这意味着我需要拥有它,但我无法获得拥有的bx
,因为当我在if let
中取消引用它时,我需要将它作为参考移动。
Is it possible or advisable to move the reference without copying it?是否可以或建议在不复制参考的情况下移动参考?
#[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
}
}
I think the key issue with your mental model is that you're thinking of Box<T>
as simply a pointer.我认为你的心理 model 的关键问题是你认为Box<T>
只是一个指针。 Rust references (and most smart pointers like Box<T>
) are not merely pointers, but valid pointers. Rust 引用(以及大多数智能指针,如Box<T>
)不仅是指针,而且是有效指针。 That is, there are no null references and references must always point to valid data at all times.也就是说,没有 null 引用,并且引用必须始终指向有效数据。
When we try to do self.list = **bx;
当我们尝试做self.list = **bx;
, we're moving the data from bx
to self.list
. ,我们将数据从bx
移动到self.list
。 However, bx
doesn't own its data.但是, bx
不拥有其数据。 When the mutable borrow bx
ends, the actual owner is going to be holding invalid data.当可变借用bx
结束时,实际所有者将持有无效数据。
So what do we do?那么我们该怎么办? The simplest way is what's sometimes called Jones' Trick where we switch out the data in bx
for some dummy value.最简单的方法是有时被称为琼斯的技巧,我们将bx
中的数据切换为一些虚拟值。 Now the actual owner of the data in bx
won't be holding invalid data.现在bx
中数据的实际所有者将不会持有无效数据。 So how do we do this?那么我们该怎么做呢? This is the purview of the function std::mem::replace
which takes a mutable reference and a value and replaces the data behind the mutable reference with that value, returning what was behind the mutable reference before (including ownership.).这是 function std::mem::replace
的权限,它接受一个可变引用和一个值,并用该值替换可变引用后面的数据,返回之前可变引用后面的内容(包括所有权。)。 That's exactly what we want to do here with self.list = std::mem::replace(&mut **bx, List::Nil)
.这正是我们想要在这里使用self.list = std::mem::replace(&mut **bx, List::Nil)
做的事情。 Again, List::Nil
is just some dummy data;同样, List::Nil
只是一些虚拟数据; any List
at all would work exactly the same.任何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
}
}
To be slightly more idiomatic, instead of &mut **bx
, we could simply use bx.as_mut()
to get a mutable reference from the box.为了稍微更惯用一点,我们可以简单地使用bx.as_mut()
来代替&mut **bx
从框中获取可变引用。 Also, the into_iter
call on list_inst
is unnecessary since ListHolder
already implements Iterator
so it doesn't need to be turned into one.此外,对list_inst
的into_iter
调用是不必要的,因为ListHolder
已经实现了Iterator
所以它不需要变成一个。 You may also be wondering about num
and val
and why we still have to make a temporary variable for that.您可能还想知道num
和val
以及为什么我们仍然必须为此创建一个临时变量。
The reason is that this value is still just a reference and we don't have owned access to the owner ( self.list
).原因是这个值仍然只是一个参考,我们没有拥有对所有者 ( self.list
) 的访问权限。 That means we have to make a copy of it to return.这意味着我们必须复制它才能返回。 u32
implements Copy
so this isn't really a problem, but if you tried to make the linked list generic in the type of its elements, it simply wouldn't work. u32
实现了Copy
所以这不是一个真正的问题,但是如果你试图使链表在其元素的类型上是通用的,它根本就行不通。 let val = *num;
is the same kind of "moving out of borrowed content" that we couldn't do before.是我们以前做不到的那种“搬出借来的内容”。
The solution is to use std::mem::replace
to get ownership not just of the data behind bx
, but of the whole list.解决方案是使用std::mem::replace
不仅获得bx
背后数据的所有权,而且获得整个列表的所有权。 So if we use std::mem::replace(&mut self.list, List::Nil)
before destructuring, self.list
will be replaced with a dummy value and we'll have ownership of the actual list, including both the value and the tail of the list.因此,如果我们在解构之前使用std::mem::replace(&mut self.list, List::Nil)
, self.list
将被替换为虚拟值,我们将拥有实际列表的所有权,包括值和列表的尾部。 This also means that we can just have self.list = *bx
, as I'm sure you originally wanted.这也意味着我们可以只拥有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
}
}
}
The upshot is now you can make the list generic with hardly any effort.结果是现在您可以毫不费力地使列表通用。
If you want to learn more about how Rust's ownership model affects the implementation of linked lists, you can do no better than the excellent series Learn Rust With Entirely Too Many Linked Lists .如果你想了解更多关于 Rust 的所有权 model 如何影响链表的实现,你可以做的最好的系列是学习 Rust With Entirely Too Many Linked Lists 。 The series covers everything here in detail as well as many variations.该系列详细介绍了此处的所有内容以及许多变体。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.