21

I happened upon this problem where format! creates a temporary value in a pattern that is not anchored to anything, as far as I understand it.

let x = 42;
let category = match x {
    0...9 => "Between 0 and 9",
    number @ 10 => format!("It's a {}!", number).as_str(),
    _ if x < 0 => "Negative",
    _ => "Something else",
};

println!("{}", category);

In this code, the type of category is a &str, which is satisfied by returning a literal like "Between 0 and 9". If I want to format the matched value to a slice using as_str(), then I get an error:

error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:5:24
  |
3 |     let category = match x {
  |         -------- borrow later stored here
4 |         0...9 => "Between 0 and 9",
5 |         number @ 10 => format!("It's a {}!", number).as_str(),
  |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        - temporary value is freed at the end of this statement
  |                        |
  |                        creates a temporary which is freed while still in use
  |
  = note: consider using a `let` binding to create a longer lived value
  = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

I have done some reading, and found people with similar problems, but I couldn't seem to find any solution.

A simple workaround would be to have category be a String instead of a &str, but I don't like the idea of having to put .to_string() on the end of every literal in the pattern, as it's not as clean.

Is there a way to solve the problem, or do I just need to work around it?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Bjarke Pedersen
  • 213
  • 2
  • 6

3 Answers3

14

This is 90% a duplicate of Return local String as a slice (&str), see that for multiple other solutions.

There's one extra possibility since this is all in one function: You can declare a variable for the String and only set it when you need to allocate. The compiler (obliquely) suggests this:

consider using a let binding to create a longer lived value

fn main() {
    let x = 42;
    let tmp;

    let category = match x {
        0...9 => "Between 0 and 9",
        number @ 10 => {
            tmp = format!("It's a {}!", number);
            &tmp
        }
        _ if x < 0 => "Negative",
        _ => "Something else",
    };

    println!("{}", category);
}

This is mostly the same as using a Cow, just handled by the compiler instead of a specific type.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • The solution only works if the match arm is a literal, otherwise rust doesn't compile since `tmp` can be assigned twice. – Pawan Kumar Feb 02 '21 at 12:54
  • @PawanKumar please provide a playground example showing what you mean. The first match arm in this example does not require a string literal; that’s literally the point of the answer. – Shepmaster Feb 02 '21 at 12:57
  • https://play.rust-lang.org/?https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b235475ee2cd1713e5d72e922a0352c Matching against a fixed number works, but if it is dynamic, then the better solution would be Own the string or use a Rc. – Pawan Kumar Feb 02 '21 at 13:21
  • This specific example the solution works I just wanted to point out the other solutions as well – Pawan Kumar Feb 02 '21 at 13:22
  • 1
    @PawanKumar that has nothing to do with a number, [it has to do with the loop](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c69bed6ff892492df0d07b6f3806c649). The "other solutions" are already mentioned in the first line of this answer. However, [the `Cow` solution](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=67c0a041d424fd720db61a11695c2b1d) , as mentioned in this answer, involves less memory allocation than a required `String`. – Shepmaster Feb 02 '21 at 13:57
3

format! can't return &str because it will always allocate String. What is possible to do is to return a &str from a String, which is what you did in your code.

As the compiler hinted, the created String is immediately dropped after its creation because it went out of the current scope and one way around could be an external variable that is not bounded to the match scope. E.g.:

use std::fmt::Write;

fn main() {
    let mut buffer = String::with_capacity(20);
    buffer.push_str("It's a ");

    let x = 10;
    let category = match x {
        0...9 => "Between 0 and 9",
        number @ 10 => {
            write!(&mut buffer, "{}", number).unwrap();
            buffer.as_str()
        }
        _ if x < 0 => "Negative",
        _ => "Something else",
    };

    println!("{}", category);
}

If you want an [no_std] environment or don't want to do any dynamic allocation, you can take a look at this limited code snippet:

use core::str;

fn each_digit<F>(mut number: u32, mut f: F)
where
    F: FnMut(u8),
{
    while number > 0 {
        f((number % 10) as u8);
        number /= 10;
    }
}

fn main() {
    const BUFFER_LEN: usize = 20;
    let mut buffer = [0u8; BUFFER_LEN];

    let x = 12344329;
    let category = match x {
        0...9 => "Between 0 and 9",
        number @ 123443219 => {
            let mut idx = BUFFER_LEN;
            each_digit(number, |digit| {
                let ascii = digit + 48;
                idx -= 1;
                buffer[idx] = ascii;
            });
            str::from_utf8(&buffer[idx..BUFFER_LEN]).unwrap()
        },
        _ => "Something else",
    };

    assert_eq!("123443219", category);
}
Caio
  • 3,178
  • 6
  • 37
  • 52
  • 2
    You can use `write!(&mut buffer, "{}", number)` to save an allocation + copy in the first snippet, and I think it will also work under `no_std` with a fixed buffer (untested). – trent Jan 16 '19 at 20:40
  • 2
    [How to write an integer as a string to a byte array with no_std?](https://stackoverflow.com/q/39488327/155423) – Shepmaster Jan 16 '19 at 20:52
0

In my case How to overcome "temporary value dropped while borrowed" when converting an i32 to &str
I could solve it by moving the call inside the branches

pub fn uidl(&mut self, message_number: Option<i32>) -> POP3Result {
    let command = match message_number {
        Some(_) => POP3Command::UidlOne,
        None => POP3Command::UidlAll,
    };

    match message_number {
        Some(i) => {
            // Here the value is not dropped because it is not leaving the scope
            self.execute_command(command, Some(arg.to_string().as_str()))
        }
        // Here I had to duplicate the call
        None => self.execute_command(command, None),
    }
}

Kind of what is suggested in the error message https://doc.rust-lang.org/error-index.html#E0597

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567