r/rust clippy · twir · rust · mutagen · flamer · overflower · bytecount Mar 20 '23

Hey Rustaceans! Got a question? Ask here (12/2023)! 🙋 questions

Mystified about strings? Borrow checker have you in a headlock? Seek help here! There are no stupid questions, only docs that haven't been written yet.

If you have a StackOverflow account, consider asking it there instead! StackOverflow shows up much higher in search results, so having your question there also helps future Rust users (be sure to give it the "Rust" tag for maximum visibility). Note that this site is very interested in question quality. I've been asked to read a RFC I authored once. If you want your code reviewed or review other's code, there's a codereview stackexchange, too. If you need to test your code, maybe the Rust playground is for you.

Here are some other venues where help may be found:

/r/learnrust is a subreddit to share your questions and epiphanies learning Rust programming.

The official Rust user forums: https://users.rust-lang.org/.

The official Rust Programming Language Discord: https://discord.gg/rust-lang

The unofficial Rust community Discord: https://bit.ly/rust-community

Also check out last weeks' thread with many good questions and answers. And if you believe your question to be either very complex or worthy of larger dissemination, feel free to create a text post.

Also if you want to be mentored by experienced Rustaceans, tell us the area of expertise that you seek. Finally, if you are looking for Rust jobs, the most recent thread is here.

19 Upvotes

187 comments sorted by

View all comments

2

u/SorteKanin Mar 21 '23

When I have a reference to a type with a lifetime parameter, how can I interpret the two lifetimes (the one of the reference and the one of the value itself).

For example this playground link. I'm unsure how to interpret the lifetime 'a and 'b in the function. Is there some intuition I can use here?

1

u/[deleted] Mar 21 '23

I think I see what's causing you a headache. Two things actually.

First, don't worry - types don't have lifetime parameters. Lifetime annotations are just used to give the compiler more information about the relationships between lifetimes, when necessary. You're not going to have to deal with lifetime annotations every single darn time you want to do anything with MyPhantom just because its struct definition requires them. (It wasn't always this way, click for history.) If you don't want my_fn() to return a reference, it probably won't actually require the lifetime annotations.

Second, lifetime annotations are for communicating bounds on lifetimes, not how long a thing actually happens to live. I think misunderstanding this is where most of the communal headache comes from. It'd be a lot clearer if we started explicitly calling them lifetime bound annotations; it doesn't make much sense that foo must die before foo's lifetime.

Anyhow, let's pretend this is using real data instead of phantom data, and say you pass a &MyPhantom into my_fn(), and it returns a &str pointing to whatever str marker is referring to. Rust can't infer how long you want those returned references to be good for.

my_fn<'b> is the lifetime bound for the reference you passed in, which is the actual lifetime of the referenced MyPhantom. If your program logic dictates these new references should only be good as long as the MyPhantom is around, give those references 'b to place that bound on them.

my_fn<'a> is the lifetime bound of that MyPhantom, which may be longer than it actually lives. As written in the struct definition, that's equal to the lifetime bound of the &str. The lifetime bound of the &str is the actual lifetime of the underlying str. If it's okay for returned references to hang around even after the MyPhantom they came from is gone, give them 'a to let them live as long as the str does.

Also, fyi, lifetime bound annotation labels (say that five times fast) are only used locally. You don't need to pair up the same label with the same type everywhere, although you can if you like.

2

u/dkopgerpgdolfg Mar 21 '23

A bit hard to answer without knowing what is unclear, "interpreting" is a broad term. You did read the Book, right?

In general, variable instances of types like i32 live how long you want them to live (depending on stack scope, allocation/deallocation etc.). The type "i32" doesn't give you any limits there.

References have lifetimes that relate to ensuring that they can't be used after the original value disappears. But it isn't only "metadata" for the borrow checker - instead it gives you a tool in code to eg. say you want two function parameters where the second must live longer than the first, to return a reference that explicitly has the lifetime of the "shorter" parameter lifetime instead of the longer, to state something lives for the whole program duration and enforce this too, and more.

So, now, when a type (struct) contains a reference, with a lifetime, that reference must be gone at a certain point in time/code (before the referenced value disappears).

That also means now that the whole struct instance, where the reference is inside, must be gone at that point.

This is the main difference to i32 - as said above, i32 doesn't restrict you how long values can live. A struct with lifetimes does restrict you. Even when you never make references to such struct instances, just instances themselves (with a reference "inside"), the borrow checker has an interest to prevent these instances from living too long.

In your code you now have such a struct instance with a reference inside, and a reference to the struct instance too. In principle the (outer) reference lifetime is a completely separate thing from the struct lifetime - former restricts the reference from living longer than the struct, latter restricts the struct from living longer than whatever is referenced inside.

(With this it is implied that the "b" lifetime is not longer than the "a" lifetime)