Skip to content

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

Closed

Description

@SCdF

(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.

Activity

  1. 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[/+] on Mar 13, 2018
  2. sfackler commented on Mar 13, 2018

    @sfackler
    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.

  3. SCdF commented on Mar 13, 2018

    @SCdF
    Author

    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.

  4. estebank commented on Mar 14, 2018

    @estebank
    Contributor

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

  5. SCdF commented on Mar 14, 2018

    @SCdF
    Author

    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

  6. estebank commented on Mar 14, 2018

    @estebank
    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 :-/

  7. SCdF commented on Mar 15, 2018

    @SCdF
    Author

    @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.

  8. Ixrec commented on Mar 16, 2018

    @Ixrec
    Contributor

    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.

  9. SCdF commented on Mar 16, 2018

    @SCdF
    Author

    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

  10. added
    C-enhancementCategory: An issue proposing an enhancement or a PR with one.
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on May 21, 2018
  11. oskgo commented on Jan 25, 2024

    @oskgo
    Contributor

    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.

  12. Dylan-DPC commented on Jan 26, 2024

    @Dylan-DPC
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-diagnosticsArea: Messages for errors, warnings, and lintsC-enhancementCategory: An issue proposing an enhancement or a PR with one.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions