Borrowing string works only outside of `match` block

Viewed 54

I am pretty new to Rust, an I'm currently having an issue with the borrow checker. This code doesn't compile:

    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut out = String::new();
        let mut xs = self.xs.iter();
        for x in 0..11 {
            out += match x {
                3 | 7 => "|",
                _ => &xs.next().unwrap().to_string(),
            };
        }
        write!(f, "{}", out)
    }

I get the error

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:32:23
   |
30 |               out += match x {
   |  ____________________-
31 | |                 3 | 7 => "|",
32 | |                 _ => &xs.next().unwrap().to_string(),
   | |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
   | |                       |                            |
   | |                       |                            temporary value is freed at the end of this statement
   | |                       creates a temporary which is freed while still in use
33 | |             };
   | |_____________- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

However, this very similar code does compile: (of course, not with the functionality I'm looking for)

    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut out = String::new();
        let mut xs = self.xs.iter();
        for x in 0..11 {
            out += &xs.next().unwrap().to_string();
        }
        write!(f, "{}", out)
    }

My question is, why does the first example not work if the second one does? My understanding is that the match should just be evaluated as what it's returning, so in the case where x is not 3 or 7, it should be functionally the same as the second example.

Thank you!

1 Answers

The arms in a match are allowed to be blocks with their own statements. The impact of that is that temporaries created in a match arm are dropped when leaving the arm. The temporary String created by xs.next().unwrap().to_string() is thus destroyed before the add-assign can execute, but the arm is attempting to return a borrow against the temporary, resulting in a dangling reference.

The second example you show doesn't have this problem because it doesn't use match (or another block construct like if) and so the temporary exists until after the add-assign is performed.

One way around this is to have both arms of the match return a String:

out += match x {
    3 | 7 => "|".to_string(),
    _     => xs.next().unwrap().to_string(),
};

(If you needed the match to evaluate as a &str you could borrow the result with &match x { ... } but that's not necessary here.)

Of course, this adds an extra allocation in the 3 | 7 case, but is required to make the types of the match arms consistent.

Consider instead performing the add-assign inside of the match. With this approach, the problem of making the arm types match goes away, and the temporary lifetime issue is also resolved:

match x {
    3 | 7 => { out += "|"; }
    _     => { out += xs.next().unwrap().to_string(); }
};
Related