Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I convert a temporary String to a &str? [duplicate]

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?

like image 371
Joël Abrahams Avatar asked Jan 26 '23 14:01

Joël Abrahams


1 Answers

&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()
};
like image 124
Boiethios Avatar answered Jan 30 '23 01:01

Boiethios