Search code examples
functional-programmingrustlifetimeborrowing

Folding over references inside a match results in a lifetime error


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?


Solution

  • There is a difference between the result of your branches:

    • "" is of type &'static str
    • content is of type i32, so you are converting it to a String and then from that to a &str... but this &str has the same lifetime as the String returned by to_string, which dies too early

    A quick work-around, as mentioned by @Dogbert, is to move acc + inside the branches:

    let s: String = vec.iter().fold(
        String::new(),
        |acc, &ref e| match e.get_first() {
            None => acc,
            Some(ref content) => acc + &content.to_string(),
        }
    );
    

    However, it's a bit wasteful, because every time we have an integer, we are allocating a String (via to_string) just to immediately discard it.

    A better solution is to use the write! macro instead, which just appends to the original string buffer. This means there are no wasted allocations.

    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
        }
    );
    

    It's maybe a bit more complicated, notably because formatting adds in error handling, but is more efficient as it only uses a single buffer.