Learn You a Rust IV - Lifetime Fenangling
This is the fourth part of the Learn You a Rust for Great Good! tutorial explainer series. If you’re coming to the series for the first time, I recommend starting at the first post linked above.
For those who’ve subscribed to my RSS feed (and whose periodic requests I have seen passing by in vain in my nginx log), apologies for not writing anything sooner. I know, the last time
I wrote stuff was April. But don’t worry, I’ve come back after procrastinating forever working on my personal audio project with some more irksome lifetime problems,
and how to solve them.
Last time, in LYaR III, we looked at the most basic kinds of lifetime
problems the enterprising new Rustacean faces: storing a reference in a struct
, and dealing with more than one reference in
a function signature. This time, I’m going to give you some examples of lifetime-related quarrels I’ve personally had with the
Rust compiler, and how I and it came to an amicable compromise.
Combining objects and contexts
Last time, we talked about how objects’ lifetimes are linked to their containing block, and how they are allocated & deallocated in order. Here’s a refresher example:
All good, right? This doesn’t seem too hard to understand. However, you do need to be acutely aware of this rule, because there are times where, if you’re not concentrating, it’ll leave you utterly stumped. Here’s an example of what I’m talking about:
This is my attempt at creating a wonderful firework show in Rust. I’ve been a bit naughty and stolen the Rust Book’s
firework set, but I’m sure they won’t mind. (To compensate, I’ve invited @steveklabnik over to watch the show.) My FireworkShow
object contains all you need for a good show - a start, middle, and finale, and a Drop
impl, so that when the show goes out
of scope, it sets off all the fireworks by drop()
ping them as well. I’m only taking a reference to the fireworks, using the
&'a
lifetime syntax to ensure the fireworks don’t explode before the show starts (that is, the fireworks last for as long
as the show), whilst still ensuring people can gaze at the fireworks before the show starts (because we’ve only taken a &
borrow).
But what’s this? The rustc
-firework-safety-analysis-team won’t let me start the show!
fireworks.rs:29:24: 29:35 error: `firecracker` does not live long enough fireworks.rs:29 show.start = Some(&firecracker); ^~~~~~~~~~~ fireworks.rs:25:11: 31:2 note: reference must be valid for the block at 25:10... fireworks.rs:25 fn main() { ^ fireworks.rs:27:48: 31:2 note: ...but borrowed value is only valid for the block suffix following statement 1 at 27:47 fireworks.rs:27 let firecracker = Firework { strength: 1 }; ^ fireworks.rs:30:25: 30:28 error: `tnt` does not live long enough fireworks.rs:30 show.finale = Some(&tnt); ^~~ fireworks.rs:25:11: 31:2 note: reference must be valid for the block at 25:10... fireworks.rs:25 fn main() { ^ fireworks.rs:28:42: 31:2 note: ...but borrowed value is only valid for the block suffix following statement 2 at 28:41 fireworks.rs:28 let tnt = Firework { strength: 100 }; ^ error: aborting due to 2 previous errors
So, what’s going on here? Reading the compiler errors, it’s non-obvious to me what’s going on (okay, some of the more seasoned
Rustaceans will spot the problem instantly, but let’s act as if I have no experience and I’m trusting rustc
for help):
- it seems to say that my fireworks don’t live long enough. That seems fair, let’s see the notes it’s given us:
- ah, alright, the fireworks must be valid for the entire block. That makes sense, because we need them for our show.
- But, hang on, they’re only living as long as the statement that declares them? That doesn’t make any sense!
It turns out that the compiler diagnostics aren’t really helpful here in fixing our problem. (That’s why I’m here, to tell you the solution after having to muddle through this myself.) The problem here is simple: remember, objects are deallocated in the reverse order from when they were declared. So this happens: (code snippet copied here for your reference)
- Alright, end of main(), everybody, let’s start deallocating stuff.
- Okay, so looks like
tnt
was declared first, let’s kill that. (BOOM!) - Next up is
firecracker
, so let’sDrop
that one too… (BOOM!) - And, finally,
show
- wait, just a darn minute,show
still has a borrow offirecracker
andtnt
!!! Quick, unexplode them soshow
doesn’t try todrop
random memory when we kill it!!!
Obviously, we can’t have show
containing invalid references. We need to rewrite our function so that the show
is only declared
AFTER the fireworks are good and ready:
Perfect! Our firework show is safe, and Rust has ensured that none of the fireworks will go off before we want them to.
Boxing the fireworks up for a firework convention
I’ve also started to think, however, that our simple Firework
type is not enough - what if we want to add a more complex firework, like
one of those ones which sits on the ground and spurts out sparks everywhere? Our code doesn’t account for this situation!
(There are so many exploding things we’re missing out on!)
A particular example of a use-case which we’ll need this capability for is the new FireworkConvention
. They’ve got all sorts
of new types of fireworks there, and they also feature entire shows!
To solve this problem, then, I’m going to add a new trait
, Explodable
, for explodey things like fireworks, and we’ll store
our fireworks as boxed trait objects (Box<Explodable>
) that’ll let us store any kind of firework! I’ll put this together,
along with the new FireworkConvention
, in the below example.
Okay. We’ve made a new FireworkConvention
object, which stores Vec
s (dynamic arrays) of all the different types of exploding
things one can find in such a convention - small stands of people with fireworks, sponsored exhibits from the likes of the
famous FireworkCorp®, and big firework shows like ours. All of these things are nicely packed away in Boxes
where the only
thing they can do is tell the world how explodey they are (with strength()
) and explode (via Drop
). If you ask me, it’s
a weird convention.
However, just as the convention is about to start, a bunch of Rust Firework Convention Safety officials walk in. They’ve got a few queries about our convention.
fireworks.rs:47:24: 47:35 error: `firecracker` does not live long enough fireworks.rs:47 show.start = Some(&firecracker); ^~~~~~~~~~~ fireworks.rs:47:24: 47:35 note: reference must be valid for the static lifetime... fireworks.rs:43:48: 53:2 note: ...but borrowed value is only valid for the block suffix following statement 0 at 43:47 fireworks.rs:43 let firecracker = Firework { strength: 1 }; ^ fireworks.rs:48:25: 48:28 error: `tnt` does not live long enough fireworks.rs:48 show.finale = Some(&tnt); ^~~ fireworks.rs:48:25: 48:28 note: reference must be valid for the static lifetime... fireworks.rs:45:42: 53:2 note: ...but borrowed value is only valid for the block suffix following statement 2 at 45:41 fireworks.rs:45 let tnt = Firework { strength: 100 }; ^ error: aborting due to 2 previous errors
However, this time, their demands seem to be different.
fireworks.rs:47:24: 47:35 note: reference must be valid for the static lifetime...
The static lifetime? Say what now? Oh, yeah, I sort of remember that thing. That’s the thing I told you about earlier in part III, where we needed to send stuff between threads. Let me quote myself:
The 'static lifetime is a special lifetime which means “lives for the entire program”.
Hang on. Just a darn minute. Is Rust saying that to attend the firework convention, items must ensure that they live forever?
That doesn’t make that much sense, because then they can’t have any references inside them! Our show
will never make it to
the big convention.
Or will it? Just like the previous example, this is another time where rustc
is a bit unhelpful. When it tells us that reference must be valid for the static lifetime
,
it’s actually because we told it that the FireworkConvention only accepts things with a static lifetime.
“But when? How?! I swear, officer, I never wrote any code with the word 'static
in my life!”
Turns out that when we Box
ed up our fireworks, Rust assumed that we meant 'static
. By default, Rust assumes the content
inside a Box lives for the static lifetime - that is, it doesn’t contain any references or anything like that. If you want
something else, you need to tell Rust by adding + 'a
inside the Box<>
part, like so:
Once we do this, Rust realises that we actually wanted to allow our explodey things to be non-'static
(after all, the
FireworkCorp® doesn’t live forever - it’s bound to go bankrupt, what with Brexit recently and all that). Our code compiles, and
the firework convention is allowed to continue. Hooray!
Conclusion (because my examples are massive and confusing)
So, if you remember nothing about the crazy firework show metaphors, the things you need to take away from this are the following:
- If you’re assembling a larger
struct
out of references to smallerstruct
s, declare all the stuff being referenced before all the stuff you’re assembling it into. - If you’re boxing up trait objects that don’t need to be
'static
, tell the Rust compiler as it won’t do it for you.
Thanks for reading! If you have any comments, queries, or suggestions, either make a comment on Reddit /r/rust (thread here) or hit me up on Twitter @eeeeeta9. I’m going to try to stick to a more rigid schedule, so subscribe to my RSS feed for more Learn You a Rust!