2

I have the following code that works fine:

fn main() {
    let mut example = String::new();

    if 1 + 1 == 2 {
        example += &"string".to_string()
    } else {
        example += &'c'.to_string()
    };

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

When I change the code to this:

fn main() {
    let mut example = String::new();

    example += if 1 + 1 == 2 {
        &"string".to_string()
    } else {
        &'c'.to_string()
    };

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

I get the following error:

error[E0597]: borrowed value does not live long enough
 --> src/main.rs:5:10
  |
5 |         &"string".to_string()
  |          ^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
6 |     } else {
  |     - temporary value dropped here while still borrowed
7 |         &'c'.to_string()
8 |     };
  |     - temporary value needs to live until here

error[E0597]: borrowed value does not live long enough
 --> src/main.rs:7:10
  |
7 |         &'c'.to_string()
  |          ^^^^^^^^^^^^^^^ temporary value does not live long enough
8 |     };
  |     - temporary value dropped here while still borrowed
  |
  = note: values in a scope are dropped in the opposite order they are created

This makes no sense to me as both snippets seem identical. Why doesn't the second snippet work?

1
  • 1
    Hopefully it's an artifact of your example, but this code is not idiomatic. Especially code like &"string".to_string(). Commented Oct 30, 2018 at 19:44

2 Answers 2

4

You've already seen an explanation as to why this code cannot be compiled. Here's some code that works and is closer to your goal:

example += &if 1 + 1 == 2 {
    "string".to_string()
} else {
    'c'.to_string()
};

I would not claim this to be idiomatic Rust. One thing that sticks out to me is the needless allocation of "string" into a String. I'd write this code using String::push_str and String::push:

if 1 + 1 == 2 {
    example.push_str("string");
} else {
    example.push('c');
}

If you weren't appending the string, I'd just evaluate it directly:

let example = if 1 + 1 == 2 {
    "string".to_string()
} else {
    'c'.to_string()
};

I might even use dynamic dispatch (although it's less likely):

let s: &std::fmt::Display = if 1 + 1 == 2 { &"string" } else { &'c' };
let example = s.to_string();

or

use std::fmt::Write;
let mut example = String::new();
let s: &std::fmt::Display = if 1 + 1 == 2 { &"string" } else { &'c' };
write!(&mut example, "{}", s).unwrap();

See also:

Sign up to request clarification or add additional context in comments.

2 Comments

@trentcl I am embarrassed. On the up side, check out what I found by going the silly way.
Thanks for the details on idiomatic Rust for this kind of thing. Have an upvote!
3

When you apply & to expressions, Rust automatically creates anonymous variables that own the result of the evaluation of the expression. So, your code is roughly equivalent to

fn main() {
    let mut example = String::new();

    example += if 1 + 1 == 2 {
        let temp1 = "string".to_string();
        &temp1
    } else {
        let temp2 = 'c'.to_string();
        &temp2
    };

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

As you can now hopefully clearly see, the scope (and the lifetime) of temp1 is restricted to the true-branch of the if-expression, and the scope of temp2 is restricted to the false-branch of the if-expression. Neither scope / lifetime extends outside of the if-expression, so the Strings inside the both branches of if cannot be appended to example.

In contrast to that, your first example is roughly equivalent to

fn main() {
    let mut example = String::new();

    if 1 + 1 == 2 {
        let temp1 = "string".to_string();
        example += &temp1;
    } else {
        let temp2 = 'c'.to_string();
        example += &temp2;
    };

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

and in both cases temp1 and temp2 live long enough so that the content of Strings can be copied and appended to example before temp1 and temp2 are dropped.

8 Comments

@trentcl You are right. I reformulated the last sentence. Hope it makes more sense now.
@AndreyTyukin Wait... I'm still confused by this. Why can't a copy occur in the first example too? I mean, you say that the variables (temp1 and temp2) are both restricted to their respective branches for the first example, but isn't this also true for the second? Why can a copy occur for the first one but not the second?
@ArnavBorborah the variables temp1 and temp2 never leave their respective curly braces.
@ArnavBorborah In both cases, temp1 and temp2 are dropped when their block ends at a closing curly brace. But the appending += takes place inside of the branches in one case, and outside of the branches in the other case. If += is outside of the branches, then there are no buffers that could be appended to example, because both temp1 and temp2 have already been dropped. If += is inside of the branches, then everything is fine, because the content of the strings can be appended to example with += before temp1 or temp2 is dropped.
Perhaps @Shepmaster meant something like this, which does work, and is ugly. :-)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.