折叠匹配内的引用会导致生命周期错误

时间:2016-08-30 15:38:16

标签: functional-programming rust lifetime borrowing

我希望通过遍历简单结构的向量来构建字符串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,我怎样才能使编译器理解我希望字符串引用生效?

2 个答案:

答案 0 :(得分:6)

您的分支结果之间存在差异:

  • ""的类型为&'static str
  • content属于i32类型,因此您要将其转换为String,然后再将其转换为&str ...但此&strString返回的to_string具有相同的生命周期,而acc +过早死亡

正如@Dogbert所提到的,快速解决方法是将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 } ); 宏,它只是附加到原始字符串缓冲区。这意味着没有浪费的分配。

var user = require('./users');
router.post('/login', user.post('/login'));

它可能有点复杂,特别是因为格式化会增加错误处理,但是因为它只使用单个缓冲区而更有效。

答案 1 :(得分:3)

您的问题有多种解决方案。但首先是一些解释:

  

如果to_string()首先返回&str,则编译器不会抱怨。那么,有什么区别?

假设有一个方法to_str()返回&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())不需要任何堆分配,因此不会有性能损失。

但是,我们仍然每个整数分配一次。要获得更有效的解决方案,请参阅Matthieu M.'s answer