如何在没有克隆的情况下更改对拥有的值的引用? [英] How can I change a reference to an owned value without clone?

查看:54
本文介绍了如何在没有克隆的情况下更改对拥有的值的引用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在练习我在阅读 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 所以它不需要变成了一个.您可能还想知道 numval 以及为什么我们仍然需要为此创建一个临时变量.

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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆