在涉及线和地图的情况下对所有权感到困惑
Confused about ownership in situations involving lines and map
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 的 ownership/borrowing 规则在非平凡情况下的工作方式知之甚少,尽管我认为我至少了解基础知识。因此,如果有人能给我一个相对清晰和全面的指南,说明所有这些示例中发生了什么,以及如何以某种直接的方式修复失败的示例,我将非常感激!
键是 lines
产生的值的类型:&str
。为了避免不必要的克隆,lines
实际上 returns 引用了它所调用的字符串的切片,当你将它收集到 Vec
时,Vec
的元素只是对字符串切片的引用。因此,当然,当您的函数退出并删除字符串时, Vec
内的引用将被删除并无效。请记住,&str
是借用的字符串,而 String
是拥有的字符串。
解析有效,因为您获取了那些 &str
,然后将它们读入 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<Item = &i32>
,您可以简单地使用 Iterator::cloned
克隆每个元素,以便它们不再参考。
另一种解决方案是将 String
作为参数,因此它以及对它的引用可以超出函数的范围:
fn problem(my_string: &str) -> Vec<&str> {
my_string.lines().collect()
}
这里的问题是这一行:
let my_string = String::from("First Line\nSecond Line");
将字符串数据复制到堆上分配的缓冲区(因此不再 'static
)。然后 lines
returns 引用该堆分配的缓冲区。
注意&str
also implements a lines
method,这样就不用把字符串数据复制到堆中了,直接用你的字符串就可以了:
fn problem() -> Vec<&'static str> {
let my_string = "First Line\nSecond Line";
my_string.lines().collect()
}
这避免了所有不必要的分配和复制。
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 的 ownership/borrowing 规则在非平凡情况下的工作方式知之甚少,尽管我认为我至少了解基础知识。因此,如果有人能给我一个相对清晰和全面的指南,说明所有这些示例中发生了什么,以及如何以某种直接的方式修复失败的示例,我将非常感激!
键是 lines
产生的值的类型:&str
。为了避免不必要的克隆,lines
实际上 returns 引用了它所调用的字符串的切片,当你将它收集到 Vec
时,Vec
的元素只是对字符串切片的引用。因此,当然,当您的函数退出并删除字符串时, Vec
内的引用将被删除并无效。请记住,&str
是借用的字符串,而 String
是拥有的字符串。
解析有效,因为您获取了那些 &str
,然后将它们读入 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<Item = &i32>
,您可以简单地使用 Iterator::cloned
克隆每个元素,以便它们不再参考。
另一种解决方案是将 String
作为参数,因此它以及对它的引用可以超出函数的范围:
fn problem(my_string: &str) -> Vec<&str> {
my_string.lines().collect()
}
这里的问题是这一行:
let my_string = String::from("First Line\nSecond Line");
将字符串数据复制到堆上分配的缓冲区(因此不再 'static
)。然后 lines
returns 引用该堆分配的缓冲区。
注意&str
also implements a lines
method,这样就不用把字符串数据复制到堆中了,直接用你的字符串就可以了:
fn problem() -> Vec<&'static str> {
let my_string = "First Line\nSecond Line";
my_string.lines().collect()
}
这避免了所有不必要的分配和复制。