在匹配中折叠引用会导致生命周期错误
Folding over references inside a match results in a lifetime error
我想通过遍历简单结构的向量来构建字符串 s
,根据结构向 acc
附加不同的字符串。
#[derive(Clone, Debug)]
struct Point(Option<i32>, Option<i32>);
impl Point {
fn get_first(&self) -> Option<i32> {
self.0
}
}
fn main() {
let mut vec = vec![Point(None, None); 10];
vec[5] = Point(Some(1), Some(1));
let s: String = vec.iter().fold(
String::new(),
|acc, &ref e| acc + match e.get_first() {
None => "",
Some(ref content) => &content.to_string()
}
);
println!("{}", s);
}
运行 此代码导致以下错误:
error: borrowed value does not live long enough
Some(ref content) => &content.to_string()
^~~~~~~~~~~~~~~~~~~
note: reference must be valid for the expression at 21:22...
|acc, &ref e| acc + match e.get_first() {
^
note: ...but borrowed value is only valid for the expression at 23:33
Some(ref content) => &content.to_string()
^~~~~~~~~~~~~~~~~~~~
问题是我创建的 &str
的生命周期似乎立即结束。但是,如果 to_string()
首先会返回 &str
,编译器就不会抱怨。那么,有什么区别呢?
如何让编译器理解我希望字符串引用在我构造时一直存在 s
?
您的问题有多种解决方案。但首先是一些解释:
If to_string()
would have returned a &str
in the first place, the compiler would not have complained. Then, what is the difference?
假设有一个方法to_str()
,returns一个&str
。签名会是什么样子?
fn to_str(&self) -> &str {}
为了更好地理解这个问题,让我们添加明确的生命周期(由于生命周期省略,这不是必需的):
fn to_str<'a>(&'a self) -> &'a str {}
很明显,返回的 &str
与方法的接收者 (self
) 一样长。这没问题,因为接收器的寿命足以满足您的 acc + ...
操作。但是,在您的情况下, .to_string()
调用会创建一个新对象,该对象仅存在于第二个匹配臂中。手臂本体离开后,便会被摧毁。因此,您不能将对它的引用传递给外部范围(其中 acc + ...
发生)。
所以一个可能的解决方案看起来像这样:
let s = vec.iter().fold(
String::new(),
|acc, e| {
acc + &e.get_first()
.map(|f| f.to_string())
.unwrap_or(String::new())
}
);
这不是最优的,但幸运的是你的默认值是一个空字符串,并且空字符串的拥有版本 (String::new()
) 不需要任何堆分配,所以没有性能损失。
但是,我们仍然为每个整数分配一次。有关更有效的解决方案,请参阅 .
你的分支结果有差异:
""
的类型是 &'static str
content
是 i32
类型,因此您要将其转换为 String
,然后再将其转换为 &str
... 但是此 &str
与 to_string
返回的 String
具有相同的生命周期,后者死得太早
如@Dogbert 所述,一个快速的解决方法是将 acc +
移动到分支内:
let s: String = vec.iter().fold(
String::new(),
|acc, &ref e| match e.get_first() {
None => acc,
Some(ref content) => acc + &content.to_string(),
}
);
然而,这有点浪费,因为每次我们有一个整数时,我们都在分配一个 String
(通过 to_string
)只是为了立即丢弃它。
更好的解决方案是改用 write!
宏,它只是附加到原始字符串缓冲区。这意味着没有浪费的分配。
use std::fmt::Write;
let s = vec.iter().fold(
String::new(),
|mut acc, &ref e| {
if let Some(ref content) = e.get_first() {
write!(&mut acc, "{}", content).expect("Should have been able to format!");
}
acc
}
);
它可能有点复杂,主要是因为格式化增加了错误处理,但效率更高,因为它只使用一个缓冲区。
我想通过遍历简单结构的向量来构建字符串 s
,根据结构向 acc
附加不同的字符串。
#[derive(Clone, Debug)]
struct Point(Option<i32>, Option<i32>);
impl Point {
fn get_first(&self) -> Option<i32> {
self.0
}
}
fn main() {
let mut vec = vec![Point(None, None); 10];
vec[5] = Point(Some(1), Some(1));
let s: String = vec.iter().fold(
String::new(),
|acc, &ref e| acc + match e.get_first() {
None => "",
Some(ref content) => &content.to_string()
}
);
println!("{}", s);
}
运行 此代码导致以下错误:
error: borrowed value does not live long enough
Some(ref content) => &content.to_string()
^~~~~~~~~~~~~~~~~~~
note: reference must be valid for the expression at 21:22...
|acc, &ref e| acc + match e.get_first() {
^
note: ...but borrowed value is only valid for the expression at 23:33
Some(ref content) => &content.to_string()
^~~~~~~~~~~~~~~~~~~~
问题是我创建的 &str
的生命周期似乎立即结束。但是,如果 to_string()
首先会返回 &str
,编译器就不会抱怨。那么,有什么区别呢?
如何让编译器理解我希望字符串引用在我构造时一直存在 s
?
您的问题有多种解决方案。但首先是一些解释:
If
to_string()
would have returned a&str
in the first place, the compiler would not have complained. Then, what is the difference?
假设有一个方法to_str()
,returns一个&str
。签名会是什么样子?
fn to_str(&self) -> &str {}
为了更好地理解这个问题,让我们添加明确的生命周期(由于生命周期省略,这不是必需的):
fn to_str<'a>(&'a self) -> &'a str {}
很明显,返回的 &str
与方法的接收者 (self
) 一样长。这没问题,因为接收器的寿命足以满足您的 acc + ...
操作。但是,在您的情况下, .to_string()
调用会创建一个新对象,该对象仅存在于第二个匹配臂中。手臂本体离开后,便会被摧毁。因此,您不能将对它的引用传递给外部范围(其中 acc + ...
发生)。
所以一个可能的解决方案看起来像这样:
let s = vec.iter().fold(
String::new(),
|acc, e| {
acc + &e.get_first()
.map(|f| f.to_string())
.unwrap_or(String::new())
}
);
这不是最优的,但幸运的是你的默认值是一个空字符串,并且空字符串的拥有版本 (String::new()
) 不需要任何堆分配,所以没有性能损失。
但是,我们仍然为每个整数分配一次。有关更有效的解决方案,请参阅
你的分支结果有差异:
""
的类型是&'static str
content
是i32
类型,因此您要将其转换为String
,然后再将其转换为&str
... 但是此&str
与to_string
返回的String
具有相同的生命周期,后者死得太早
如@Dogbert 所述,一个快速的解决方法是将 acc +
移动到分支内:
let s: String = vec.iter().fold(
String::new(),
|acc, &ref e| match e.get_first() {
None => acc,
Some(ref content) => acc + &content.to_string(),
}
);
然而,这有点浪费,因为每次我们有一个整数时,我们都在分配一个 String
(通过 to_string
)只是为了立即丢弃它。
更好的解决方案是改用 write!
宏,它只是附加到原始字符串缓冲区。这意味着没有浪费的分配。
use std::fmt::Write;
let s = vec.iter().fold(
String::new(),
|mut acc, &ref e| {
if let Some(ref content) = e.get_first() {
write!(&mut acc, "{}", content).expect("Should have been able to format!");
}
acc
}
);
它可能有点复杂,主要是因为格式化增加了错误处理,但效率更高,因为它只使用一个缓冲区。