在匹配中折叠引用会导致生命周期错误

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
  • contenti32 类型,因此您要将其转换为 String,然后再将其转换为 &str... 但是此 &strto_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
    }
);

它可能有点复杂,主要是因为格式化增加了错误处理,但效率更高,因为它只使用一个缓冲区。