如何在没有克隆的情况下更改对拥有的值的引用? [英] How can I change a reference to an owned value without clone?
问题描述
我正在练习我在阅读 The Book 时学到的 Rust 概念.我已经能够通过复制 Box
并将 list
分配给复制的框来迭代我的 List
枚举,但直觉上,我觉得就像必须有一种方法可以使其指向行中的下一个指针".
I'm practicing Rust concepts that I've learned while reading The Book. 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".
如果我尝试在没有 bx.clone()
的情况下执行此操作,如下所示:self.list = **bx
,我得到无法移出 **bx
位于可变引用后面."这意味着我需要拥有它,但我无法获得拥有的 bx
,因为当我在 if let
中取消引用它时,我需要将它作为引用移动.
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." 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
.
是否可以移动引用而不复制它?
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
}
}
推荐答案
我认为您的心智模型的关键问题是您将 Box
视为一个简单的指针.Rust 引用(以及大多数智能指针,如 Box<T>
)不仅是指针,而且是 有效 指针.也就是说,没有空引用,并且引用必须始终指向有效数据.
I think the key issue with your mental model is that you're thinking of Box<T>
as simply a pointer. Rust references (and most smart pointers like Box<T>
) are not merely pointers, but valid pointers. That is, there are no null references and references must always point to valid data at all times.
当我们尝试执行 self.list = **bx;
时,我们将数据从 bx
移动到 self.list
.但是,bx
不拥有它的数据.当可变借用 bx
结束时,实际所有者将持有无效数据.
When we try to do self.list = **bx;
, we're moving the data from bx
to self.list
. However, bx
doesn't own its data. When the mutable borrow bx
ends, the actual owner is going to be holding invalid data.
那我们怎么办?最简单的方法是有时称为 Jones' Trick 我们将 bx
中的数据切换为一些虚拟值.现在 bx
中数据的实际所有者将不会持有无效数据.那么我们如何做到这一点呢?这是函数 std::mem::replace
的权限,它接受一个可变引用和一个值,并用该值替换可变引用后面的数据,返回之前可变引用后面的数据(包括所有权!).这正是我们想要用 self.list = std::mem::replace(&mut **bx, List::Nil)
做的事情.同样,List::Nil
只是一些虚拟数据;任何 List
都完全一样.
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. Now the actual owner of the data in bx
won't be holding invalid data. 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!). That's exactly what we want to do here with self.list = std::mem::replace(&mut **bx, List::Nil)
. Again, List::Nil
is just some dummy data; any List
at all would work exactly the same.
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
}
}
为了稍微更惯用一点,我们可以简单地使用 bx.as_mut()
代替 &mut **bx
来从框中获取可变引用.此外,list_inst
上的 into_iter
调用是不必要的,因为 ListHolder
已经实现了 Iterator
所以它不需要变成了一个.您可能还想知道 num
和 val
以及为什么我们仍然需要为此创建一个临时变量.
To be slightly more idiomatic, instead of &mut **bx
, we could simply use bx.as_mut()
to get a mutable reference from the box. 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. You may also be wondering about num
and val
and why we still have to make a temporary variable for that.
原因是这个值仍然只是一个引用,我们没有对所有者的拥有访问权(self.list
).这意味着我们必须复制它才能返回.u32
实现了 Copy
所以这不是一个真正的问题,但是如果你试图使链表的元素类型通用,它根本就行不通.let val = *num;
是同一种我们以前做不到的搬出借来的内容".
The reason is that this value is still just a reference and we don't have owned access to the owner (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. let val = *num;
is the same kind of "moving out of borrowed content" that we couldn't do before.
解决方案是使用std::mem::replace
不仅获得bx
后面数据的所有权,而且获得整个列表的所有权.所以如果我们在解构之前使用 std::mem::replace(&mut self.list, List::Nil)
, self.list
将被替换为一个哑元值,我们将拥有实际列表的所有权,包括列表的值和尾部.这也意味着我们可以只使用 self.list = *bx
,正如我确定您最初想要的那样.
The solution is to use std::mem::replace
to get ownership not just of the data behind bx
, but of the whole list. 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. This also means that we can just have self.list = *bx
, as I'm sure you originally wanted.
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.
如果你想了解更多关于 Rust 的所有权模型如何影响链表的实现,你可以做的没有比优秀系列更好的了 使用过多的链表学习 Rust.该系列详细介绍了此处的所有内容以及许多变体.
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. The series covers everything here in detail as well as many variations.
这篇关于如何在没有克隆的情况下更改对拥有的值的引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!