Rust uses types to automatically manage memory. The owned String type tells the compiler to free it. The borrowed &str tells the compiler NOT to free it.

You can't simply mix the string types, because that's a type error:

let label = if x != 0 {
    x.to_string() // this has allocated memory: it's a String
} else {
    "zero" // this is a shared constant: &'static str
};

But Rust can keep track of sometimes-initialized variables:

let tmp;
let label = if x != 0 {
    tmp = x.to_string();  // tmp is a String
    &tmp // borrowing gives a &str, compatible with the other &'static str
} else {
    "zero"
};

Now the label can be always a &str, and the compiler will know when to free data in the tmp variable (it will add a hidden bool to track when it's been initialized).

Rust 1.79 will be able to do this trick automatically in some cases (implicitly creating a hidden variable like the tmp in this example).

Another approach is the Cow type that internally holds a bool (or equivalent) keeping track whether its content should be freed or not.

See it in the Rust Playground.