Help with making a string live outside of if block

Hey Rustaceans,
Can you help me how to make a string outlive an if block?

let mut majorBrand: &str = &String::from("UNKNOWN_BRAND");

if mode.eq("SomeMode") {
     majorBrand = getMajorBrand(10).as_str();
}

println!("majorBrand: {}", majorBrand);
// Rest of the code that uses majorBrand

With this I get an error that says

> "temporary value dropped while borrowed
> consider using a let binding to create a longer lived value"

Without getting into advanced features such as lifetimes, Box, etc - is there no way to get the string slice out of the if block?

I am avoiding to pass the original &str to the function getMajorBrand and modifying over there, because I want retain the default value "UNKNOWN_BRAND" if the mode String is not "SomeMode"

I have completed around 80% rustlings exercises, but I feel so dumb for being bummed out by such a simple thing at the start of a personal project.

Thanks

You can take the compiler's suggestion of using a let. Here is how to do it in this case. I also fixed some style and removed the unnecessary String::from.

let mut major_brand: &str = "UNKNOWN_BRAND";

let major_brand_string: String; // this variable outlives the if body
if mode == "SomeMode" {
    major_brand_string = get_major_brand(10);
    major_brand = major_brand_string.as_str();
}

println!("majorBrand: {major_brand}");

You could also make major_brand simply hold a String instead of &str, owning the string data. That is simplest, but incurs an extra allocation every time.

let mut major_brand: String = String::from("UNKNOWN_BRAND");

if mode == "SomeMode" {
    major_brand = get_major_brand(10);
}

println!("majorBrand: {major_brand}");

In either case, you should also consider using an if else instead of a mutable variable:

let major_brand_string: String;
let major_brand: &str = if mode == "SomeMode" {
    major_brand_string = get_major_brand(10);
    major_brand_string.as_str()
} else {
    "UNKNOWN_BRAND"
};
5 Likes

Thanks a lot!

This was indeed what I went with initially, but for some reason I got UNKNOWN_BRAND in the output.
I guess the if condition did not match - and I wrongly assumed (too much rustlings & docs) that the String returned by get_major_brand(10) would have been freed at the if block end. That made me dig through the rabbit role and back to the straight forward one again. Now I realise all String values are created in heap and so they can live outside the block. Thank you.

let mut major_brand: &str = "UNKNOWN_BRAND";

let major_brand_string: String; // this variable outlives the if body
if mode == "SomeMode" {
    major_brand_string = get_major_brand(10);
    major_brand = major_brand_string.as_str();
}

println!("majorBrand: {major_brand}");

I believe this one is elegant - and as you say performant as well.

The last one you shared is ninja code :), I have seen it elsewhere while searching for tips.. thanks for showing it in the context of what I am trying to do. (I believe that's called a closure?) - not needing to make the variable mutable as well. Beautiful.

Cheers!

@simplicity360 the term closure usually refers to an anonymous (without a name) function. In rust that looks like this: (|val| { val.do_something() })

I think the if { ... } else { ... } solution @kpreid suggested is demonstrating the concept of "shadowing" in Rust: Scope and Shadowing - Rust By Example

1 Like

There's no shadowing, and no closures; it's just blocks returning expressions.

fn example() -> i32 {
    0 // <--- the value the function returns
}
let x = {
    0 // <-- the value the block "returns"
};
let x = if condition {
    0 // <-- the value of the if/else when `condition` is `true`
} else {
    13 // <-- the value of the if/else when `condition` is `false`
};
// Same idea
let major_brand: &str = if mode == "SomeMode" {
    major_brand_string.as_str()
} else {
    "UNKNOWN_BRAND"
};

The only other thing going on is conditional initialization.

let major_brand_string: String; // new
let major_brand: &str = if mode == "SomeMode" {
    major_brand_string = get_major_brand(10); // new
    major_brand_string.as_str()
} else {
    "UNKNOWN_BRAND"
};

major_brand_string is initialized if you take the true branch, but never initialized if you take the false branch.

The compiler is smart enough to make sure that

  • You can't use major_brand_string unless it's definitely initialized (checked at compile time)
  • The destructor later runs if and only if it was initialized (when required, this is accomplished with a runtime check; often the check can be optimized out)
3 Likes

Thanks @quinedot , clear explanation.
@emushack - good that you shared the shadowing link, coming from other languages I am still getting used to "bindings" concept.