1

I want to build a string s by iterating over a vector of simple structs, appending different strings to acc depending on the struct.

#[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);

}

Running this code results in the following error:

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()
                                 ^~~~~~~~~~~~~~~~~~~~

The problem is that the lifetime of the &str I create seems to end immediately. However, if to_string() would have returned a &str in the first place, the compiler would not have complained. Then, what is the difference?

How can I make the compiler understand that I want the string references to live as long as I am constructing s?

4

2 回答 2

6

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

  • ""是类型&'static str
  • content是 type i32,所以你将它转换为 a String,然后从 that 转换为 a &str...但这与返回的&str生命周期相同,它过早死亡Stringto_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(via 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
    }
);

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

于 2016-08-30T15:59:13.493 回答
3

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

如果to_string()一开始就返回 a &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. 的回答

于 2016-08-30T15:55:56.003 回答