Moving let declaration by two lines and hells break loose


#1

Hi,

I’m stumbled across error that can’t understand at all, and it boggles my mind :confused:
Here is code that compiles ok:

let yaml = load_yaml!("cli.yml");
let cfg = App::from_yaml(yaml).get_matches();
let mut config_path = "/etc/telebot.conf";
if let Some(v) = cfg.value_of("config") {
    config_path = v;
}

When I move my config_path declaration two lines up so I have declaration first I’m getting compilation error. Why oh why I can’t have my variables at the beginning ?
Code that doesn’t compile:

let mut config_path = "/etc/telebot.conf";
let yaml = load_yaml!("cli.yml");
let cfg = App::from_yaml(yaml).get_matches();
if let Some(v) = cfg.value_of("config") {
    config_path = v;
    // println!("Hura")
}

Here is cryptic error message:

<clap macros>:2:3: 3:31 error: borrowed value does not live long enough
<clap macros>:2 & :: clap :: YamlLoader :: load_from_str ( include_str ! ( $ yml ) ) . expect
                  ^
src/main.rs:13:16: 13:37 note: in this expansion of load_yaml! (defined in <clap macros>)
src/main.rs:12:47: 175:2 note: reference must be valid for the block suffix following statement 0 at 12:46...
src/main.rs:12     let mut config_path = "/etc/telebot.conf";
                                                             ^
src/main.rs:13:38: 175:2 note: ...but borrowed value is only valid for the block suffix following statement 1 at 13:37
src/main.rs:13     let yaml = load_yaml!("cli.yml");
                                                    ^
src/main.rs:16:22: 16:25 error: `cfg` does not live long enough
src/main.rs:16     if let Some(v) = cfg.value_of("config") {
                                    ^~~
src/main.rs:12:47: 175:2 note: reference must be valid for the block suffix following statement 0 at 12:46...
src/main.rs:12     let mut config_path = "/etc/telebot.conf";
                                                             ^
src/main.rs:14:50: 175:2 note: ...but borrowed value is only valid for the block suffix following statement 2 at 14:49
src/main.rs:14     let cfg = App::from_yaml(yaml).get_matches();
                                                                ^
error: aborting due to 2 previous errors

#2

My guess would be that you can’t assign a reference with a small lifetime to a reference with large lifetime.

And in the second example the lifetime for config_path is bigger than the lifetime for cfg.value_of, because it is declared earlier.

If I were you, I would avoid using mutable local variables altogether:

let config_path = cfg.value_of("config").unwrap_or("/etc/telebot.conf") 

Default can also be extracted as const DEFAULT: &'static str.


#3

Another option is to use shadowing:

let config_path = "/etc/telebot.conf";
let yaml = load_yaml!("cli.yml");
let cfg = App::from_yaml(yaml).get_matches();
let config_path = cfg.value_of("config").unwrap_or(config_path);

#4

Not 100% certain, but this is almost certainly because v is borrowed from cfg. Storage is destroyed in reverse lexical order. In the first example, config_path gets destroyed first, then cfg. In the second, your code requires cfg to be destroyed before config_path, and config_path might contain a pointer into cfg; thus, it’s possible that config_path could become a dangling pointer. Rust will not allow this. This is what it means by "cfg does not live long enough".

Why oh why I can’t have my variables at the beginning ?

Because Rust is trying to protect you from yourself. :slight_smile:

Simplest solution is to just put the variables in a safe order. If you’re pathologically opposed to that, you can switch to using String instead of &str, so that the value is independent of cfg (i.e. config_path = v.to_string()).


#5

The nightlies give a nicer error message though:

struct Foo(String);

impl Foo {
    fn inner(&self) -> &str { &self.0 }
}

fn main() {
    let mut s = "none";
    let foo = Foo("abc".into());
    s = foo.inner();
}
error: `foo` does not live long enough
  --> <anon>:10:9
   |
10 |     s = foo.inner();
   |         ^^^ does not live long enough
11 | }
   | - borrowed value dropped before borrower
   |
   = note: values in a scope are dropped in the opposite order they are created

#6

Thanks,
I still can’t crab that moving line in source code affects lifetime of it such drastically.
I’ll try to forget about my “C” philosophy of declaring everything I need in one place at the beginning.

Thanks again.


#7

This slightly expanded example should make it more clear. Any reference borrowed by bar needs to live at least as long as bar itself but since bar is created before foo and therefore destroyed after foo it’s not allowed to borrow things from foo.

struct Foo {
    s: String,
}

impl Foo {
    fn inner(&self) -> &str {
        &self.s
    }
    
}

struct Bar<'a> {
    r: &'a str,
}

impl<'a> Drop for Bar<'a> {
    fn drop(&mut self) {
        println!("I've borrowed and still can see '{}'", self.r);
    }
}

fn main() {
    let mut bar = Bar { r: "none" };
    let foo = Foo { s: "abc".to_string() };
    bar.r = foo.inner();
    // values in a scope are dropped in the opposite order they are created
    // i.e. foo is destroyed before bar
    // but bar's destructor attempts to access the reference borrowed from foo
}

#8

This example might be clearer?

fn main() {
    {
        let a = 1;
        {
            let mut _b;
            _b = &a;
        } //end of scope for _b
    } // end  scope for a
}

will compile just fine, where as

fn main() {
    {
        let mut _b;
        {
            let a = 1;
            _b = &a;
        } //end of scope for a
        // _b still refers to a, but a has died.
    } // end  scope for _b
}

will not. As I understand it, every variable declaration starts an implicit block; I’ve just made them explicit here.