Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lifetime complaints depend on whether you attach the value to a variable first #48998

Closed
SCdF opened this issue Mar 13, 2018 · 10 comments
Closed
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@SCdF
Copy link

SCdF commented Mar 13, 2018

(Apologies if this is just me misunderstanding Rust, I'm very new to the language.)

Given:

#[derive(Debug)]
struct Foo<'a> {
    bar: &'a Bar
}
#[derive(Debug)]
struct Bar {
    val: i32
}

fn main() {
    let foo = foo_me();
    println!("{:?}", foo);
}

The following implementation of foo_me works:

fn foo_me<'a>() -> Foo<'a> {
    Foo {
        bar: &Bar {
            val: 42
        }
    }
}

While this one doesn't:

fn foo_me<'a>() -> Foo<'a> {
    let bar = Bar {
        val: 42
    };
    Foo {
        bar: &bar
    }
}

With:

  --> src/main.rs:15:15
   |
15 |         bar: &bar
   |               ^^^ borrowed value does not live long enough
16 |     }
17 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 10:1...
  --> src/main.rs:10:1
   |
10 | fn foo_me<'a>() -> Foo<'a> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^

I can see what it's complaining about, but I feel like these two examples should function identically. I have a more complicated piece of code that I'm not smart enough to coerce into the working format, so there may be more complicated variants on this which are not solvable.

@SCdF SCdF changed the title Lifetime complaints depend on whether you attach a the value to a variable first Lifetime complaints depend on whether you attach the value to a variable first Mar 13, 2018
@sfackler
Copy link
Member

That's an intended result of static rvalue promotion.

With that, a &Bar { val: 42 } essentially turns into static BAR: Bar = Bar { val: 42 }; &BAR, so the working implementation produces a Foo<'static> ('static is what's required by the signature of foo_me). However, the broken version takes a reference to a local variable of the function, and its lifetime only lasts until the function returns, not for ever as is required by foo_me.

@SCdF
Copy link
Author

SCdF commented Mar 13, 2018

Oh I see, because:

fn foo_me<'a>() -> Foo<'a> {
    static BAR: Bar = Bar {
        val: 42
    };
    Foo {
        bar: &BAR
    }
}

Also works. I wonder if it's possible to improve the error message to make this clearer. I realise we can't have every edge case documented there though, then it would make it less clear.

@estebank
Copy link
Contributor

I feel that the first case should emit a warning about lifetime 'a being elided to 'static.

@oli-obk oli-obk added the A-diagnostics Area: Messages for errors, warnings, and lints label Mar 14, 2018
@SCdF
Copy link
Author

SCdF commented Mar 14, 2018

It's also worth mentioning that if the compiler fails to elide[1] the lifetime to static for another reason (eg the value is not allowed to be static because it's a trait or in its instantiation you're calling a function) it just silently doesn't work.

So if you add this:

impl Bar {
    fn new(val: i32) -> Bar {
        Bar {
            val: val
        }
    }
}

And then try to do this:

fn foo_me<'a>() -> Foo<'a> {
    Foo {
        bar: &Bar::new(42)
    }
}

You get the error about borrowing that we got with the let example, because Rust can't handle statics that involve function calls, so it silently doesn't elide to static, so we're back to square one.

To a noob this rule in general seems really confusing, and involves you knowing all the compiler rules involved to understand why your code doesn't compile (as opposed to the error clearly explaining what you did wrong, because it's not that the borrowed value won't live long enough, it's actually because it couldn't elide it to static).

[1] Rust's usage of this word really confuses me, but that's neither here nor there

@estebank
Copy link
Contributor

Rust's usage of (elide in the context of lifetimes) really confuses me, but that's neither here nor there

You could read it as "ensures it outlives/outlasts"/"lives long enough to survive X". Any suggestions on how to improve the wording, not only the diagnostic themselves, to make the subject friendlier to newcomers are welcome. After a while when you understand the concept is very hard to un-understand it to see the failings with fresh eyes :-/

@SCdF
Copy link
Author

SCdF commented Mar 15, 2018

@estebank to me elide means to omit something. So when people talk about lifetime elision, it sounds like removing lifetimes? But I think it's actually about adding implicit lifetimes? Or converting lifetimes?

I think a specific section in the handbook on what that word means would be really helpful. I imagine it's too late to change the word. I realise there is this:

The patterns programmed into Rust’s analysis of references are called the lifetime elision rules. These aren’t rules for programmers to follow; the rules are a set of particular cases that the compiler will consider, and if your code fits these cases, you don’t need to write the lifetimes explicitly.
But it's like 5 paragraphs deep into a section, and doesn't really gel with what you just said?

I find my flow for confusion is:

  • Make a complicated struct
  • Have it not compile and complain about not implementing Sized (it's not clear that I can't just implement that myself, it's just a trait like any other), oh right structs have to be a constant size
  • Or, ^ but with function signatures
  • So you change the offending element to a reference because references are constant size, now you need to add lifetimes for a reason that I don't really understand yet,
  • Then that in turn makes other things really complicated, because now you're not passing around the data and that causes all sorts of lifetime issues that are super complicated

I have read the chapter on lifetimes but I really feel like I need to read it 2-3 more times. My background is completely in memory-safe languages, which may make the concept more complicated for me.

It would be really cool if there was some kind of site / git repo where you progressively solve problems that are caused by lifetimes, borrowing, and generally more esoteric Rust concepts that people from major languages aren't used to. Sort of like 4clojure / project euler but specifically to get people comfortable with layering all of these techniques on top of each other.

@Ixrec
Copy link
Contributor

Ixrec commented Mar 16, 2018

It would be really cool if there was some kind of site / git repo where you progressively solve problems that are caused by lifetimes, borrowing, and generally more esoteric Rust concepts that people from major languages aren't used to. Sort of like 4clojure / project euler but specifically to get people comfortable with layering all of these techniques on top of each other.

@SCdF I think https://github.com/carols10cents/rustlings is meant to be something like this, though it hasn't made it to the "esoteric" parts of Rust yet.

@SCdF
Copy link
Author

SCdF commented Mar 16, 2018

Yep I have these links queued up:

Hopefully enlightenment will be in there somewhere.

Ideally though, Rust's documentation itself would contain this kind of thing, as it would be more discoverable, and if getting being noob friendly is important then I think that's critical

@XAMPPRocky XAMPPRocky added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 21, 2018
@oskgo
Copy link
Contributor

oskgo commented Jan 25, 2024

The diagnostics for the two examples mentioned are much nicer now after #59114:

Original case
#[derive(Debug)]
struct Foo<'a> {
    bar: &'a Bar
}
#[derive(Debug)]
struct Bar {
    val: i32
}

fn main() {
    let foo = foo_me();
    println!("{:?}", foo);
}

fn foo_me<'a>() -> Foo<'a> {
    let bar = Bar {
        val: 42
    };
    Foo {
        bar: &bar
    }
}
error[E0515]: cannot return value referencing local variable `bar`
  --> src/main.rs:19:5
   |
19 | /     Foo {
20 | |         bar: &bar
   | |              ---- `bar` is borrowed here
21 | |     }
   | |_____^ returns a value referencing data owned by the current function

For more information about this error, try `rustc --explain E0515`.
Case from comments
#[derive(Debug)]
struct Foo<'a> {
    bar: &'a Bar
}
#[derive(Debug)]
struct Bar {
    val: i32
}

impl Bar {
    fn new(val: i32) -> Bar {
        Bar {
            val: val
        }
    }
}

fn foo_me<'a>() -> Foo<'a> {
    Foo {
        bar: &Bar::new(42)
    }
}
error[E0515]: cannot return value referencing temporary value
  --> src/lib.rs:19:5
   |
19 | /     Foo {
20 | |         bar: &Bar::new(42)
   | |               ------------ temporary value created here
21 | |     }
   | |_____^ returns a value referencing data owned by the current function

For more information about this error, try `rustc --explain E0515`.

I've made a new issue (#120344) for the suggestion in #48998 (comment).

It doesn't seem to me like there's much actionable left here, so I think this should be closed.

@Dylan-DPC
Copy link
Member

Closing this as it is completed and we have #120344 to track the related changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

8 participants