无法移动借用内容 / 无法移动共享引用后面

196

我不理解错误信息cannot move out of borrowed content。我曾多次收到这个错误并成功解决,但我从未明白其中的原因。

例如:

for line in self.xslg_file.iter() {
    self.buffer.clear();

    for current_char in line.into_bytes().iter() {
        self.buffer.push(*current_char as char);
    }

    println!("{}", line);
}

产生错误:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:31:33
   |
31 |             for current_char in line.into_bytes().iter() {
   |                                 ^^^^ cannot move out of borrowed content

在较新版本的Rust中,错误信息是:

error[E0507]: cannot move out of `*line` which is behind a shared reference
  --> src/main.rs:31:33
   |
31 |             for current_char in line.into_bytes().iter() {
   |                                 ^^^^ move occurs because `*line` has type `std::string::String`, which does not implement the `Copy` trait

我通过克隆 line 解决了这个问题:

for current_char in line.clone().into_bytes().iter() {

阅读了以下其他贴子后,我仍然不理解这个错误:

为什么会出现这种错误?


1
你看过这样的问题吗(https://dev59.com/Zl4c5IYBdhLWcg3wVpFI)?(顺便说一下,字符串提供了`.bytes()`方法。) - huon
是的,我研究了一下,但是没太明白 :( 而且我的字符串是一个 std::string::String 类型的,根据文档中的说明,没有 .bytes() 方法。 - Peekmo
4
它被称为.as_bytes() - bluss
事实上,谢谢,使用as_bytes()而不进行克隆就可以工作。但我仍然不明白为什么? - Peekmo
Stringstr 中获取 bytes 方法。 - huon
2个回答

165

让我们看一下into_bytes的签名:

fn into_bytes(self) -> Vec<u8>

这里需要传入的是 self,而不是对 self 的引用(&self)。这意味着 self 将被消耗并且在调用后将不再可用。作为替代,你会得到一个 Vec<u8>。前缀 into_ 是表示这种方法的常见方式。

我不确定你的 iter() 方法返回什么,但我的猜测是它返回了一个 &String 的迭代器,也就是说它返回了对 String 的引用,但是没有给你所有权。这意味着你不能调用消耗值的方法

正如你所发现的,一种解决方法是使用 clone。这会创建一个你拥有的副本对象,并可以在其上调用 into_bytes。正如其他评论者提到的,你还可以使用 as_bytes,它接受 &self,因此可以在已借用的值上工作。你应该根据指针的最终目标来决定使用哪种方法。

从更广阔的角度来看,这一切都与所有权的概念有关。某些操作取决于拥有该项,而其他操作可以通过借用对象(可能是可变的)进行。引用 (&foo) 不授予所有权,它只是借用。

为什么在函数的参数中使用 self 而不是 &self 很有趣?

转移所有权通常是一个有用的概念,因为当我完成某件事时,其他人可能会拥有它。在 Rust 中,这是一种更高效的方式。我可以避免分配副本、给你一个副本,然后丢弃我的副本。所有权还是最宽松的状态;如果我拥有一个对象,我可以随心所欲地使用它。


这里是我创建用于测试的代码:

struct IteratorOfStringReference<'a>(&'a String);

impl<'a> Iterator for IteratorOfStringReference<'a> {
    type Item = &'a String;

    fn next(&mut self) -> Option<Self::Item> {
        None
    }
}

struct FileLikeThing {
    string: String,
}

impl FileLikeThing {
    fn iter(&self) -> IteratorOfStringReference {
        IteratorOfStringReference(&self.string)
    }
}

struct Dummy {
    xslg_file: FileLikeThing,
    buffer: String,
}

impl Dummy {
    fn dummy(&mut self) {
        for line in self.xslg_file.iter() {
            self.buffer.clear();

            for current_char in line.into_bytes().iter() {
                self.buffer.push(*current_char as char);
            }

            println!("{}", line);
        }
    }
}

fn main() {}

4

这个问题被标记为重复,让人感到沮丧this question,因为这是 Rust 新手的另一个死胡同的例子。对于新手来说,从这些具体的答案中归纳总结起来非常困难,这意味着他们很快就会被下一个例子困住(参考:我自己)。

为了提供一点归纳总结,帮助解决“无法移动”的问题,需要考虑为什么问题首先会出现。这里有两个主要原因:

  1. 你不想声明拥有某个东西。
  2. 你想声明拥有某个东西。

第一种情况下,您无意中做了一些需要拥有权的事情。解决方案是找到一种不需要所有权的替代品。这正是本问题和答案所概述的情况。与其使用消耗其输入并要求拥有所有权的into_bytes()而应该使用as_bytes(),它将完成相同的工作而不需要声明所有权。

解决方案不明显的原因是编译器假定您确实想使用line,并帮助您将其替换为*line。因此,它会抛出误导性错误,告诉您取消引用没有帮助。您会困惑于如何修复无法move的问题,从而导致像clone()这样的临时解决方法。

实际上,您从一开始就不想取消引用,只需要找到适用于借用引用的替代方法即可。

现在,在第二种情况(这是我在顶部提到的“重复”问答中更相关的情况)中,错误是相同的,但您无意中借用而不是声明所有权。当实现方法时,通常会使用对self的引用,而不是消耗它。顺便说一下,这是完全可选的 - 方法可以选择消耗而不是借用self,尽管我无法想象除非该方法返回一个Self,否则这很实用。

在第二种情况下,令人困惑的是,绝望的clone()似乎又一次解决了问题,至少在短期内是这样。但考虑到根本原因,根本修复方法略有不同。既然您确实想要声明所有权(也许您正在尝试删除数据或将其转移给另一个所有者),那么您需要明确表示。

在简单的情况下,这可能只涉及从变量来源中删除& - 无论是您调用的函数的返回值还是当前函数的参数。

通常情况下,你无法进行改变。相反,你必须剥夺所有权。但是你不能让当前所有者手空无一物 - 你需要留给他们一些有效的东西。我所知道的唯一方法是使用std::mem中的takeswapreplace之一。在1.40中发布的take是最简单的方法 - 它只是给所有者提供了Default值,并将当前值的所有权交给了你。如果类型没有Default,则swapreplace提供了以前所有者的其他内容,这尤其重要。

这里有一个示例:

pub struct SimpleLinkedList<T> {
    head: Option<Box<Node<T>>>
}

pub struct Node<T> {
    value: T,
    next: Option<Box<Node<T>>>
}

impl<T> SimpleLinkedList<T> {
    pub fn pop(&mut self) -> Option<T> {
        if self.head.is_none() { return None; }

        let value = self.head.unwrap().value;
        self.head = self.head.unwrap().next;
        return Some(value);
    }
}

这会抛出“无法移动位于可变引用后面的self.head”错误,因为unwrap()会消耗self

有很多方法可以改进这个问题,但是为了说明目的,只需要这些就足够了:

use std::mem;

pub struct SimpleLinkedList<T> {
    head: Option<Box<Node<T>>>
}

pub struct Node<T> {
    value: T,
    next: Option<Box<Node<T>>>
}

impl<T> SimpleLinkedList<T> {
    pub fn pop(&mut self) -> Option<T> {
        if self.is_empty() { return None; }

        let old_head = mem::take(&mut self.head);
        let old_head_unwrapped = old_head.unwrap();
        self.head = old_head_unwrapped.next;
        return Some(old_head_unwrapped.value);
    }
}

我们无论如何都要替换self.head的值,因此通过在其上调用take,我们可以声明所有权。然后我们可以自由地将其传递给unwrap()并随后覆盖原始的self.head

所以总之,为什么会出现“无法移动借用的内容/引用”的情况?要么是因为你只需要借用,并且必须确保你所做的任何事情都不会消耗它,要么是因为你实际上想要消耗并且必须拥有所有权。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接