6

I wish to convert a String created using the format! macro to a &str and assign this to a value using a let binding:

fn main() {
    let my_bool = true;
    let other = String::from("my_string");
    let result = if my_bool {
        format!("_{}", other).as_str()
    } else {
        "other"
    };

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

(Rust Playground)

When I do this, the compiler complains that the temporary String value is freed at the end of the statement (from what I've understood), meaning that I'm unable to dynamically create a &str:

error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:5:9
  |
4 |     let result = if my_bool {
  |         ------ borrow later stored here
5 |         format!("_{}", other).as_str()
  |         ^^^^^^^^^^^^^^^^^^^^^ creates a temporary which is freed while still in use
6 |     } else {
  |     - temporary value is freed at the end of this statement
  |

I've been trying to understand Rust's lifetime system, but I can't really wrap my head around this one. Rust suggests the following:

  = note: consider using a `let` binding to create a longer lived value

I wrapped format!("_{}", other) in a let binding:

fn main() {
    let my_bool = true;
    let other = String::from("my_string");
    let result = if my_bool {
        let s = format!("_{}", other);
        s.as_str()
    } else {
        "other"
    };

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

But it doesn't seem to solve the issue, as when I call as_str() on this binding it still complains that the borrowed value does not live long enough:

error[E0597]: `s` does not live long enough
 --> src/main.rs:6:9
  |
4 |     let result = if my_bool {
  |         ------ borrow later stored here
5 |         let s = format!("_{}", other);
6 |         s.as_str()
  |         ^ borrowed value does not live long enough
7 |     } else {
  |     - `s` dropped here while still borrowed

This works when I omit the whole if, but I would rather not do this as this would cause a lot of headaches in the original code base.

Also, that does seem like kind of a cop-out, because then I still wouldn't know why this fails.

How would I go about solving this in a systematic manner?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Joël Abrahams
  • 379
  • 4
  • 15
  • 1
    Why do you want a `str` in the first place? If you convert it to a `str` what would be responsible for owning that memory? https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c7d3b896cb0865e70f0ce501492de439 works great. – loganfsmyth Mar 13 '19 at 22:05
  • @loganfsmyth the reason for wanting a `str` is that this is part of a match statement which returns `str`s, and I would rather that in this case I could return a `str` instead of having to wrap every match case in a `String::from(...)` – Joël Abrahams Mar 13 '19 at 22:07
  • 5
    A `&str` is a borrowed value, so in order to return one, the memory being borrowed needs to live somewhere. If your `match` is required to return a `&str` then you are explicitly limiting yourself to returning borrowed values that already existed somewhere before the match ran, which means you can't create a new `String` in there. – loganfsmyth Mar 13 '19 at 22:09
  • @loganfsmyth oh that makes more sense. – Joël Abrahams Mar 13 '19 at 22:12
  • [The duplicate applied to your question](https://play.integer32.com/?version=stable&mode=debug&edition=2018&gist=e28770b663ae828f1bbdd5814f676d6a) (this was originally a standalone answer). – trent Mar 14 '19 at 17:41

1 Answers1

10

&str is a borrowed string, so you cannot get one from a temporary String, otherwise a reference would outlive the value it is bound to.

There are 2 solutions to palliate this problem:


You can create a new variable to store the String out of the if scope:

fn main() {
    let my_bool = true;
    let other = String::from("my_string");
    
    let result;
    let result = if my_bool {
        result = format!("_{}", other);
        result.as_str()
    } else {
        "other"
    };

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

You can use the Cow type to do what you want:

use std::borrow::Cow;

fn main() {
    let my_bool = true;
    let other = String::from("my_string");
    let result = if my_bool {
        Cow::Owned(format!("_{}", other))
    } else {
        Cow::Borrowed("other")
    };

    assert_eq!("_my_string", result);
}

Cow (for clone on write) is an enum that have either an owned or a borrowed data. In this particular case, result has the type Cow<str>.

You can simplify the notation by writing:

let result = if my_bool {
    format!("_{}", other).into()
} else {
    Cow::Borrowed("other")
};

or (matter of style):

let result: Cow<str> = if my_bool {
    format!("_{}", other).into()
} else {
    "other".into()
};
Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • 1
    I encourage moving / adding this answer to the duplicate. – Shepmaster Mar 14 '19 at 00:00
  • whats wrong with `.as_str()` ? – alexzander Mar 07 '22 at 13:59
  • @alexzander There is nothing wrong with it. What is wrong is to leak a reference to a variable outside of this variable scope: the variable `s` which is created in the `if` scope is destroyed at the end of that scope. So if the OP's code had compiled, the `result` variable would have pointed to the freed memory, which is unsafe. – Boiethios Mar 10 '22 at 18:35
  • Your first solution is wrong. You are mutating an inmutable value. `result` needs to be `mut` and now it will work. – Santiago Alejandro Torres Arag Dec 22 '22 at 14:21
  • @SantiagoAlejandroTorresArag I just copy/pasted my code and try and compile it, it works perfectly fine as is. Maybe what confuses you is the variable shadowing I use. See this version without shadowing, maybe it's clearer: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d5f20e25108bd3c60c684574ed2341cc – Boiethios Dec 23 '22 at 10:13