【问题标题】:Confused about ownership in situations involving lines and map在涉及线和地图的情况下对所有权感到困惑
【发布时间】:2021-03-14 22:07:39
【问题描述】:
fn problem() -> Vec<&'static str> {
    let my_string = String::from("First Line\nSecond Line");
    my_string.lines().collect()
}

这会因编译错误而失败:

  |
7 |     my_string.lines().collect()
  |     ---------^^^^^^^^^^^^^^^^^^
  |     |
  |     returns a value referencing data owned by the current function
  |     `my_string` is borrowed here

我了解此错误的含义 - 它是为了阻止您返回对超出范围的值的引用。查看所涉及函数的类型签名后,问题似乎出在lines 方法上,该方法借用了它所调用的字符串。但为什么这很重要?我正在遍历字符串的行以获得部分的向量,我返回的是这个“新”向量,而不是任何会(非法)直接引用my_string

(我知道我可以通过仅使用字符串文字而不是使用 String::from 转换为“拥有”字符串来非常轻松地修复这个特定示例。这是一个重现问题的玩具示例 - 在我的“真实" 编码字符串变量是从文件中读取的,所以我显然不能使用文字。)

对我来说更神秘的是,对我来说应该遇到同样问题的函数的以下变体可以正常工作:

fn this_is_ok() -> Vec<i32> {
    let my_string = String::from("1\n2\n3\n4");
    my_string.lines().map(|n| n.parse().unwrap()).collect()
}

原因不可能是map 做了一些魔术,因为这也失败了:

fn also_fails() -> Vec<&'static str> {
    let my_string = String::from("First Line\nSecond Line");
    my_string.lines().map(|s| s).collect()
}

我已经玩了很长时间了,在map 中尝试了各种不同的功能——有些通过,有些失败,老实说,我不知道有什么区别。所有这一切都让我意识到我对 Rust 的所有权/借用规则如何在不平凡的情况下工作知之甚少,尽管我认为我至少了解了基础知识。因此,如果有人能给我一个相对清晰和全面的指南,了解所有这些示例中发生了什么,以及如何以某种直接的方式修复那些失败的示例,我将非常感激!

【问题讨论】:

    标签: rust ownership


    【解决方案1】:

    键是lines:&amp;str产生的值的类型。为了避免不必要的克隆,lines 实际上返回对调用它的字符串切片的引用,当您将其收集到Vec 时,Vec 的元素只是对字符串切片的引用。因此,当然,当您的函数退出并删除字符串时,Vec 中的引用将被删除并且无效。请记住,&amp;str 是借用字符串,String 是自有字符串。

    解析有效,因为您将那些&amp;strs 读入i32,因此数据被传输到一个新值,您不再需要对原始字符串的引用。

    要解决您的问题,只需使用str::to_owned 将每个元素转换为String

    fn problem() -> Vec<String> {
        let my_string = String::from("First Line\nSecond Line");
        my_string.lines().map(|v| v.to_owned()).collect()
    }
    

    需要注意的是to_string 也可以工作,而且to_owned 实际上是ToOwned trait 的一部分,所以它对于其他借用的类型也很有用。

    对于大小值的引用(str 未调整大小,因此不适用),例如 Iterator&lt;Item = &amp;i32&gt;,您可以简单地使用 Iterator::cloned 克隆每个元素,这样它们就不再是引用了。

    另一种解决方案是将String 作为参数,因此它以及对它的引用可以超出函数的范围:

    fn problem(my_string: &str) -> Vec<&str> {
        my_string.lines().collect()
    }
    

    【讨论】:

    • 谢谢 - 这是有道理的,事实上我之前一直在搞乱.to_owned 来尝试修复问题,但无法让它工作。我现在有了,尽管我的其他几个函数都必须从采用 &amp;str 切片转换为采用拥有的 Strings。我想这是不可避免的(我现在看到我误解了 Rust 书的part,我认为它是说总是首选字符串切片,但我现在看到这只是有利于借来的Strings,而不是拥有的) .再次感谢。
    • @RobinZigmond 实际上我在第二个示例中发现了一点错误,我已将其从 &amp;String 更改为 &amp;str,因为 &amp;str 更通用。
    【解决方案2】:

    这里的问题是这一行:

    let my_string = String::from("First Line\nSecond Line");
    

    将字符串数据复制到堆上分配的缓冲区(因此不再是'static)。然后lines 返回对该堆分配缓冲区的引用。

    注意&amp;str also implements a lines method,所以不需要把字符串数据复制到堆中,直接使用你的字符串即可:

    fn problem() -> Vec<&'static str> {
        let my_string = "First Line\nSecond Line";
        my_string.lines().collect()
    }
    

    Playground

    避免了所有不必要的分配和复制。

    【讨论】:

    • 谢谢,但正如我在问题中已经说过的那样 - 我知道我可以像这样修复示例,但在我的实际情况下,字符串是动态的,而不是文字,所以我不能这样做这。使用to_owned,如另一个答案中所建议的那样,并更改使用此输出的其他函数,为我修复了它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    • 2023-04-06
    • 1970-01-01
    相关资源
    最近更新 更多