【问题标题】:How do you replace the value of a mutable variable by taking ownership of it?如何通过获取可变变量的所有权来替换它的值?
【发布时间】:2018-04-10 05:36:11
【问题描述】:

我正在使用LinkedList,我想删除所有未通过测试的元素。但是,我遇到了错误cannot move out of borrowed content

据我了解,这是因为我正在使用&mut self,因此我无权使包含的值之一无效(即移动),即使是片刻也无权构造一个新的值列表。

在 C++/Java 中,我会简单地迭代列表并删除任何符合条件的元素。由于我还没有找到删除,我将其解释为迭代、过滤和收集。

目标是避免创建临时列表、克隆值以及需要采用self 并返回“新”对象。我构建了一个产生相同错误的示例。 Playground.

use std::collections::LinkedList;

#[derive(Debug)]
struct Example {
    list: LinkedList<i8>,
    // Other stuff here
}

impl Example {
    pub fn default() -> Example {
        let mut list = LinkedList::new();
        list.push_back(-5);
        list.push_back(3);
        list.push_back(-1);
        list.push_back(6);
        Example { list }
    }

    // Simmilar idea, but with creating a new list
    pub fn get_positive(&self) -> LinkedList<i8> {
        self.list.iter()
            .filter(|&&x| x > 0)
            .map(|x| x.clone())
            .collect()
    }

    // Now, attempt to filter the elements without cloning anything
    pub fn remove_negative(&mut self) {
        self.list = self.list.into_iter()
            .filter(|&x| x > 0)
            .collect()
    }
}

fn main() {
    let mut e = Example::default();
    println!("{:?}", e.get_positive());
    println!("{:?}", e);
}

在我的实际情况中,我不能简单地使用包装对象,因为它需要从不同的地方引用并包含其他重要值。

在我的研究中,我发现了一些unsafe code,这让我质疑是否可以构造一个安全函数来以类似于std::mem::replace 的方式执行此操作。

【问题讨论】:

  • 如果它被转换成一个通用(安全)函数——这样的通用安全函数是遥不可及的。由于该代码最初来自 Stack Overflow,因此我更新了您的链接;它现在指向代码的版本,其中充斥着关于它在哪些条件下有效的警告。
  • 您的文字似乎在谈论您的真实世界示例,而您的代码只是(或多或少)最小示例(这很好!)。但也许您可以编辑文本以匹配您的代码示例。这意味着:删除听众和弱指针的提及。如果我正确理解您的问题,它基本上是“如何在不创建新列表的情况下迭代链接列表的特定元素?”,对吗?
  • @LukasKalbertodt 差不多了,我包含了额外的信息,目的是解释为什么我不能简单地消费 self 以及为什么我想避免克隆(后来意识到克隆并不可怕) .将简化...
  • 正如LinkedList 文档本身所说:几乎总是使用VecVecDeque 而不是LinkedListVec has a method to do exactly what you want .现在我们必须弄清楚如何对LinkedList 做同样的事情。
  • @Shepmaster 老实说,您可能是对的,我选择了 LinkedList 是因为它永远不需要按索引进行访问,并且会定期附加和过滤。

标签: collections iterator rust borrowing


【解决方案1】:

您可以使用临时地址std::mem::swap 您的字段,然后将其替换为您修改后的列表,如下所示。最大的缺点是创建新的 LinkedList。不知道有多贵。

pub fn remove_negative(&mut self) {
    let mut temp = LinkedList::new();
    std::mem::swap(&mut temp, &mut self.list);

    self.list = temp.into_iter()
         .filter(|&x| x > 0)
         .collect();
}

【讨论】:

  • 请注意,Rust 的内置集合通常具有非常便宜的空状态,并且 this holds for LinkedList.
  • 是的,它会起作用,是的,它不会太贵,但我试图避免它,因为从逻辑上讲它看起来很愚蠢。例如,我们为 Vec 提供了 retain 函数,它完全实现了所需的功能,而在另一种语言(如 C++/Java)中,您可以在迭代时简单地删除元素。
  • 也就是说,鉴于 Rust API 没有切换到 Vec,我认为这可能是目前最好的解决方案。
【解决方案2】:

如果目标不是克隆,您可以使用引用计数指针:Rc 上的克隆方法会增加引用计数器。

use std::collections::LinkedList;
use std::rc::Rc;

#[derive(Debug)]
struct Example {
    list: LinkedList<Rc<i8>>,
    // ...
}

impl Example {
    pub fn default() -> Example {
        let mut list = LinkedList::new();
        list.push_back(Rc::new(-5));
        list.push_back(Rc::new(3));
        list.push_back(Rc::new(-1));
        list.push_back(Rc::new(6));
        Example { list }
    }

    // Simmilar idea, but with creating a new list
    pub fn get_positive(&self) -> LinkedList<Rc<i8>> {
        self.list.iter()
            .filter(|&x| x.as_ref() > &0)
            .map(|x| x.clone())
            .collect()
    }

    // Now, attempt to filter the elements without cloning anything
    pub fn remove_negative(&mut self) {
        self.list = self.list.iter()
            .filter(|&x| x.as_ref() > &0)
            .map(|x| x.clone())
            .collect()
    }


}


fn main() {
    let mut e = Example::default();
    e.remove_negative();
    println!("{:?}", e.get_positive());
    println!("{:?}", e);
}

【讨论】:

  • 虽然在我的情况下克隆是一种选择,但我实际上使用的是 Arc,所以它现在会增加额外的同步开销。
猜你喜欢
  • 2020-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-15
  • 2020-07-10
  • 2012-01-02
  • 2016-01-31
  • 2012-02-13
相关资源
最近更新 更多