简体   繁体   English

如何在没有克隆的情况下更改对拥有值的引用?

[英]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
    }
}

(playground) (操场)

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_instinto_iter调用是不必要的,因为ListHolder已经实现了Iterator所以它不需要变成一个。 You may also be wondering about num and val and why we still have to make a temporary variable for that.您可能还想知道numval以及为什么我们仍然必须为此创建一个临时变量。

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
        }
    }
}

(playground) (操场)

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM