Love these videos! I'm tired of content talking about this language as if the viewer hasn't heard about it, there really needs to be more higher concept stuff out there.
I watched the whole video through and haven't even touched Rust yet and enjoyed it. Planning on diving in completely in the upcoming few months, but I've been holding off to mentally prepare since it sounds.. different haha.
Dude you did hella well with this video. The pace at which you speak, the visuals and the way you explain make this perfectly enjoyable and helpful. Please keep doing this
dude, you're too kind :D i do have some more vid ideas lined up, aiming to publish the next one in 1-2 months. in the meantime, my focus is getting my language usable (haven't posted about that one yet, it does have a borrow checker though 👀).
Actually, you do hear the understanding of the video's creator through his explanation, so your pedantic comment wasn't even correct @@justinreynolds6318
Watched this a couple of months ago and I'm just coming back to say: "Thank you!" Your explanation makes it really easy to reason with lifetimes in Rust.
Thanks. What I found really helpful about this explanation was being shown lifetimes as compound objects. That there are "atomic" lifetimes only correspond to a single variable and that other lifetimes are compounds of these.
While less rigorous, I've always read them as "implements" or "at least", as in ... where T "implements" AsRef or ... where 'a (lives for) at least 'b so in the `longest` example: 's1 lives for at least as long as 'out 'out's actual liveness can be determined by the compiler, I'm just making the connection (or even "chain of responsibility") between the out value's lifetime and what it relies on: the inputs.
This was great. The way lifetimes are usually taught felt like learning some incomplete mental model/abstraction to me. Having to think of "time", aka the temporal aspect of execution, is always hard, whether it's concurrency, event loops, networking, or, as they are taught, lifetimes. Thanks for providing a better mental model that is not as dependent on time.
I smashed head-first into the lifetime system about a year ago when I was working on some WASM stuff Tl;Dr I was trying to make a builder pattern struct that carried a reference to a canvas render context for efficiently batching stroke calls I wound up drowning in
8:33 I'm pretty sure 'a is the *intersection* of 'x and 'y, not the union as stated and drawn. That is, the largest lifetime which is fully contained within both 'a and 'b, rather than the smallest lifetime which contains both 'a and 'b
that section of the vid assumes you're thinking of "lifetimes" as memory regions. then, "the output can point to whatever the inputs can point to". so, "both input regions need to be contained in the output region" -> union. the intersection of the "memory regions" 'x and 'y would be empty, cause they point to different strings. (technically their intersection would be the 'static region, cause all regions contain 'static)
I'm currently learning Rust, and to rethink lifetimes as not just time, but memory space makes it so much clearer to what they mean! I like this explanation :)
Great video! I think a very important note about ‘static is that it also refers to owned values when used as a trait bound. That tripped me up when first learning, thinking I effectively required leaked resources if adding that bound
Like many others have said, this has been one of the best explanations of lifetimes. Please continue the Rust series you have a very clear and concise way of describing concepts. Judging by your videos published to subscribers ration, it looks like I'm not the only one eagerly awaiting more videos :p
hey, thanks so much! i'm currently trying to get a pretty big & exciting project off the ground before the end of the year. sadly not much time for anything else. perhaps i'll take a break at some point to make some vids, who knows :P
10:54 I understand what you're saying here, but I personally find Rust's trait system a better way of explaining this syntax. 'a: 'b means 'a implements the trait 'b. What does a lifetime mean as a trait? To implement 'a is to be valid for 'a. 'static is therefore only implemented by 'static, and 'static implements every lifetime. Otherwise, great explanation!
Good timing. I'm taking freshman-year classes and one of them is discrete computational structures the moment you mentioned lifetimes as subsets of other sets it finally clicked! 😂 two years and I finally fully understood them. Thanks for that!
that's awesome! yeah, for me, reading that article (link in description, you might enjoy it) was also the first time, i actually understood lifetimes :D
This is one of the most difficult concepts to undestand in the bigginig of learning Rust and you make it easier in this video. I really appreciate that!
Just getting into rust and one thing I wish was clearer was a way to cleanly tell the compiler "this reference will be valid during this scope" i.e. fn foo(bar: impl Iterator) does not compile but for some reason fn foo) does compile and I don't really understand why that lets the compiler accept it I appreciate that this lets it compile but I haven't guaranteed any information about that reference, only named it, but maybe this will enforce that the references in the iterator cannot go out of scope during this function call, if that is the case then I wish that was more obviously written in the lifetimes documentation.
nice explanation! i would recommend closing down your aperture and turning off continuous autofocus and autoexposure during the whiteboard scenes. that way both your face and the drawings can be in focus without any unexpected shifting during the shot
This is awesome, you have a way of breaking down complex things and using comparison that resonates with me. Looking forward to the borrow checker video.
The part about subtyping and outliving of lifetimes is so often overlooked an unexplained. The rewrite you start at 9:35 is, in my opinion, crucial and something that should have been done in The Book. I have done basically the same code (with an attempt at subtyping and variance explanation) in several youtube comments on other "What are lifetime" videos which were lacking.
Awesome video man! I feel like I couldn't fully wrap my head around lifetimes as explained in the official docs, but after watching the video I now understand the other half of the picture. Thanks for sharing your knowledge!
I'm new to Rust, and ended up developing similar intuition regarding lifetimes. Thanks for validating it - can't wait for the follow up videos. Excellent stuff!
I never coded anything in rust and I was only aware of lifetime/borrow checker existence but never guessed them to be this deep. Your video is super good nonetheless since I think I got an intuitive understanding of the issue. Thanks! :) In "example_1", I got that the lifetime of r starts when it is being assigned and also an implicit dependency is made to x's "memory region" to be valid (r is only valid if x is valid and usage outside of x being valid causes an error due to attempting to extend r's lifetime outside of x's valid scope) And in "longest" example, I understood that the lifetime of 'out depends on both the lifetime of 's1 and 's2 (both shall have valid "memory region" for 'out to be valid; value of 'out can be anywhere between 's1 and 's2 but not outside of them -> usage of 'out outside of either of 's1 or 's2 valid scope will cause an error due to attempting to extend 'out lifetime where 's1 and/or 's2 no longer have valid "memory region") One thing I don't get is why multiple lifetime parameters in functions (9:33 in video) can't also have their dependency deduced by the compiler instead of requiring explicit outlives constraints? It's more verbose and clearer for me as a reader that s1 and s2 should be allowed to have different lifetimes ('a on both makes me think their lifetimes are linked but it's not since it also has 'a on the output?). I would like to have multiple lifetime parameters but still have the compiler attempting to deduce the lifetime relations itself. Why not both? If I really want to actually tie the lifetime of s1 and s2 then I could use 'a on both but am forced to use something else (e.g. 'out) on return just to be different? And I also lose the compiler's help in deducing the lifetimes relations just because I want to tie s1 and s2's lifetimes? (for whatever reason) Wouldn't it be super simple for the compiler to do this deduction with one less lifetime even?
Subtyping can be unintuitive, but it makes perfect sense for lifetime outlives semantics. Subtype can be naturally used in place of its supertype, much like a reference with outliving lifetime can be substituted in place of another one with lifetime it outlives (assuming covariance). What I found missing about regions is that they still should carry notion of where in code they are alive, and determining what region a reference can point to would still be ultimately determined by liveness and usage. In that sense, this model seems like it would do the same but with an extra step, maybe.
yeah, the models are different sides of the same coin. both liveness and aliasing matters. since the official rust learning resources emphasize the liveness aspect, i decided to focus on the aliasing aspect for this video.
Very insightful! I thought I had a pretty good grasp on lifetimes before watching this video, but it still managed to get looking at them in a completely new way.
I already learnt what, how and why but this video explained it in more depth in a way that i realized that i didnt understood lifetimes at all. Thanks a lot!
This makes it look like the lifetime is actually a part of the _value_ of the reference rather than the _type,_ which feels _weird._ In example 2, I'd expect it to only be valid if one of the two reassignments was a let-binding, shadowing the previous r with a new r with a different lifetime, meaning a different _type_ according to the model I expected. In most places in Rust, lifetimes are seen as type parameters (either to a struct or a reference) or type constraints, so the lifetime being attached to the value is non-intuitive unless you make references the only values with polymorphic types. Then again, there are higher-rank trait bounds (for
interesting observation! yeah, the "lifetime" is in fact a part of the value, cause that's a lot more flexible. or if you think about code from more of a "functional perspective", reassignment just creates a new `let` binding, that later uses bind to. you can also make it make sense with functions, as those are "generic over" lifetimes. so you could imagine them being monomorphized for the specific lifetimes at the callsite (or you could imagine them being equivalent to their inlined body). where it perhaps gets a little weird is in structs. although you could imagine those being replaced by their aggregate parts. regarding traits, i think of them more as requirements for the type. and even though T: Trait, T: 'a, 'a: 'b use the same syntax, i think of them as completely independent things.
I find the second longest() function to be more intuitive then the first. I really don't understand how the first is translated to the second under the hood. Maybe that sub-typing and variance video will explain it
To me the way to think about lifetime annotations, from a practical perspective, is that they are just a mechanism to tell the compiler the dependency between function arguments and returned value/s; the only case where is no such dependency is when a 'static reference is returned (i.e. the use of global variables as escape hatch). The use of annotations somewhere else is too artificial and unusual IMHO. Also lifetime annotations are only used on references, so, in structs, the annotations are indirectly being applied to inner reference-type fields, possibly under some nested layers.
Thanks for the video. New to Rust as I'm trying to go through a codebase written in Rust. The shear amount of syntax in Rust is overwhelming tbh. This cleared some bits
As a pretty senior developer coming to Rust, this explanation in terms of memory was really helpful to grok the concept. Nice examples, clearly annotated too!
This makes more sense if you think of lifetimes as abstractions over memory allocations and frees attached to particular references, just like traits (interfaces) abstract over concrete types (a particular struct). The difference with traits is that traits have no ordering because Rust is intentionally monomorphic, but lifetimes must have partial ordering because memory ops must be sequential to ensure memory safety. So that’s how you get covariance and contravariance. Anyone who has worked with languages supporting subclassing like Java should be familiar with the concept of type variance found in lifetimes, once you start seeing lifetimes as part of the type signature.
8:28, hold on, ch10 of the book says it's the *shorter* of the lifetimes of x and y the example that makes me think this model is wrong goes like this: say you replaced x and y with Strings so that they aren't `static lifetime and if you tried to use l1 after making x go out of scope (and its memory was cleared), it'd still give you an error, even though it points to data owned by y, which would still be valid modeling 'a as a set of memory that l1 can point at, which contains both 'x and 'y, seems to contradict this: that would imply 'a is valid when 'x OR 'y is still valid, but the book says it's while 'x AND 'y are still valid which one is actually correct? I trust the book more than this
no, that's the thing, it's flipped! with memory regions the thing on the left is actually the smaller thing, like you'd expect (or what i'd expect, anyway :D) when something takes &'b T, you can pass &'a T, if 'a lives at least as long as 'b, or 'a is a sub-region/sub-set of 'b.
@@broom7294 right, when thinking about the lifetime, the thing on the left of 'a: 'b is greater. but when thinking about regions of memory, the thing on the left is smaller. your example doesn't compile, cause you're trying to return 'b, but 'b is not "in 'a" (what you're trying to return). that's why you need 'b: 'a in this case. then: returning a is fine, cause it already has region 'a. returning b is fine, cause it has region 'b, which is "in 'a".
The problem with lifetimes is the amount of unrealistic simple examples it's used to explain it. The majority of these examples do not translate to real world use case structure.
Outstanding !! Please more videos centered around this covering various cases that might arise. Also just wondering how would we apply this thinking in this example from the lifetimes chapter in the book: fn longest y.len() { x } else { y } } fn main() { let string1 = String::from("long string is long"); { let string2 = String::from("xyz"); let result = longest(string1.as_str(), string2.as_str()); println!("The longest string is {}", result); } } I mean how we define what region 'a is pointing to?
'static isn't just leaked allocations/the .data section of the executable a 'static lifetime constraint is also valid for any lifetime always guaranteed to outlive anything in the function/struct
Thank you! I just read the chapter in the book and was struggling to understand what the point of lifetimes are. I think they seriously need to redo their explanation.
I like to explain lifetimes as ranges on a timeline, where some ranges contain others. This way we can visualize the whole program as a “tree” spanning the duration of the program.
The region concept is confusing (an error?) at 8:40 and 10:23, The outer region means the shortest lifetime of a set of inner regions - calling this relationship using set and subset is the problem: the outer lifetime should be a subset of the inner lifetime. Both the box drawing and subset mean exactly the opposite: outer is a subset of any inner.
1:40 but they have? By your own admission later, the scopes do not change, but the lifetimes do. I understand the motivating example, but the critique seems odd
Look, I'm all for being willing to drop preconcieved notions and recontextualize things, but all I'm saying is "It all got a lot simpler once I started factoring in wave-particle duality" Is not something programmers should ever be saying. I mean its not something *_anyone_* should be saying, but I'm convinced physists are masochists which, well, okay programmers are masochists too but it's a different *_brand_* of massochism.
i have a pretty silly workflow right now :D prepare code in neovim, take screenshots, arrange in google slides, take screenshots, put over audio in davinci resolve and add orange highlight rects. the "memory to subset" animation was made with motion canvas.
I always had this thought when considering the reversed subtype relation with respect to lifetime duration that there was some kind of "duality" here (in maths when you have subset relationship, you get the reversed relation when considering the "duals" of sets instead) You put it very well with the electron analogy, memory region are like the "dual" of time when considering the lifetimes, this was the missing piece I was searching for. Thank you for the enlightment 😁
@@leddoo Just to be sure you won't be confused when looking at it, "duality" has a lot of meaning in math and there is no clear definition of "dual sets". You can define the dual of vector spaces in linear algebra or the dual of functions in convex analysis, they are related but not the same. The main idea is that you can associate an action on others for each element of a space and the "dual space" is a set of elements that act on the set you are considered in a special way (for example the inner product of the associated element with any element of your set is always of the same sign). The more constrained the base set (smaller), the easier it is for an action to act "in a special way" (to keep the same sign) on it, so more elements are in the "dual". This is how subset's relationship ends up reversed in dual space. Hope you enjoy the food for thought haha !
so that basic example on wikipedia with the complement of a set makes sense. i guess what's a bit weird is how with lifetimes, one perspective is based on sets of program points, and the other based on sets of borrows. so they have different "types". i wonder if there's some deeper reason for why the subsets are reversed in this case 🤔
@@leddoo sets' complement is a bit "too simple" and not very insightful 😅 This is closer to contravariance for function argument: the more arguments you allow as inputs of your function, the more constrained the function is if you want it to have a certain behavior (which is what you want as a dev). I think we can put it that way: as you have shown, lifetimes are some kind of typed memory regions that must satisfy certain constraints (which are coded in the types relationships) but the usual way we look at it is through their dual: programs point are how you act on it, so we are often talking about functions on lifetimes, so on memory region. That's why subtype relationships hold for the memory regions and is weirdly reversed when considering lifetimes as program points ! Obviously, this reasoning is not fully correct but reflects what I was thinking about 😁
I don't use Rust. I find it great on paper but tedious in practice (although I haven't put much time into it). I still found this video useful and interesting.
Awesome work, great video! I have an oftopic question. Do you mind sharing the font and color scheme you used for the code example. I find them very easy on the eyes and easy to read, something that I struggle with many other color schemes.
Love these videos! I'm tired of content talking about this language as if the viewer hasn't heard about it, there really needs to be more higher concept stuff out there.
I watched the whole video through and haven't even touched Rust yet and enjoyed it. Planning on diving in completely in the upcoming few months, but I've been holding off to mentally prepare since it sounds.. different haha.
This makes sense because those type of videos get the most views. More advanced content is usually in the form of articles
Dude you did hella well with this video. The pace at which you speak, the visuals and the way you explain make this perfectly enjoyable and helpful. Please keep doing this
dude, you're too kind :D
i do have some more vid ideas lined up, aiming to publish the next one in 1-2 months. in the meantime, my focus is getting my language usable (haven't posted about that one yet, it does have a borrow checker though 👀).
This is by far the best understanding of lifetimes I’ve ever heard. So much easier to understand by lifting the hood a little bit
You don't hear an understanding. You hear an explanation.
Actually, you do hear the understanding of the video's creator through his explanation, so your pedantic comment wasn't even correct @@justinreynolds6318
Watched this a couple of months ago and I'm just coming back to say: "Thank you!" Your explanation makes it really easy to reason with lifetimes in Rust.
That was the best video about lifetimes I've ever watched! Thank you, leddoo!
thanks so much! 😄
I think what’s really missing is the for
Thanks. What I found really helpful about this explanation was being shown lifetimes as compound objects. That there are "atomic" lifetimes only correspond to a single variable and that other lifetimes are compounds of these.
While less rigorous, I've always read them as "implements" or "at least", as in
... where T "implements" AsRef
or
... where 'a (lives for) at least 'b
so in the `longest` example:
's1 lives for at least as long as 'out
'out's actual liveness can be determined by the compiler, I'm just making the connection (or even "chain of responsibility") between the out value's lifetime and what it relies on: the inputs.
I thought I understood lifetimes, but this completely reframed my understanding. Well done!
please continue the series, it's good to learn new things based what others discovered through great effort
i have 2-3 more vid ideas around borrow checking. they won't come before december/january though :P
This was great. The way lifetimes are usually taught felt like learning some incomplete mental model/abstraction to me. Having to think of "time", aka the temporal aspect of execution, is always hard, whether it's concurrency, event loops, networking, or, as they are taught, lifetimes. Thanks for providing a better mental model that is not as dependent on time.
I still don’t get it…
Its been a year, how are you doing?
I smashed head-first into the lifetime system about a year ago when I was working on some WASM stuff
Tl;Dr I was trying to make a builder pattern struct that carried a reference to a canvas render context for efficiently batching stroke calls
I wound up drowning in
Wow, this is so much easier to understand than how rust by example explains it. Awesome video!
8:33 I'm pretty sure 'a is the *intersection* of 'x and 'y, not the union as stated and drawn. That is, the largest lifetime which is fully contained within both 'a and 'b, rather than the smallest lifetime which contains both 'a and 'b
that section of the vid assumes you're thinking of "lifetimes" as memory regions.
then, "the output can point to whatever the inputs can point to".
so, "both input regions need to be contained in the output region" -> union.
the intersection of the "memory regions" 'x and 'y would be empty, cause they point to different strings.
(technically their intersection would be the 'static region, cause all regions contain 'static)
Re-inventing the wheel trying to fix a problem by making the implementation overly obscure.
I'm currently learning Rust, and to rethink lifetimes as not just time, but memory space makes it so much clearer to what they mean! I like this explanation :)
this is probably the best explanation for lifetimes i've seen. its rewiring my brain.
Great video this is probably the clearest explanation of lifetimes I've heard. I think the spatial metaphor is incredibly helpful thank you so much
look mom, there are actually people who think like me!
Great video! I think a very important note about ‘static is that it also refers to owned values when used as a trait bound. That tripped me up when first learning, thinking I effectively required leaked resources if adding that bound
Like many others have said, this has been one of the best explanations of lifetimes. Please continue the Rust series you have a very clear and concise way of describing concepts. Judging by your videos published to subscribers ration, it looks like I'm not the only one eagerly awaiting more videos :p
hey, thanks so much!
i'm currently trying to get a pretty big & exciting project off the ground before the end of the year. sadly not much time for anything else. perhaps i'll take a break at some point to make some vids, who knows :P
10:54 I understand what you're saying here, but I personally find Rust's trait system a better way of explaining this syntax. 'a: 'b means 'a implements the trait 'b. What does a lifetime mean as a trait? To implement 'a is to be valid for 'a. 'static is therefore only implemented by 'static, and 'static implements every lifetime.
Otherwise, great explanation!
Good timing. I'm taking freshman-year classes and one of them is discrete computational structures the moment you mentioned lifetimes as subsets of other sets it finally clicked! 😂 two years and I finally fully understood them.
Thanks for that!
that's awesome!
yeah, for me, reading that article (link in description, you might enjoy it) was also the first time, i actually understood lifetimes :D
This is one of the most difficult concepts to undestand in the bigginig of learning Rust and you make it easier in this video. I really appreciate that!
Just getting into rust and one thing I wish was clearer was a way to cleanly tell the compiler "this reference will be valid during this scope"
i.e. fn foo(bar: impl Iterator) does not compile but for some reason
fn foo) does compile and I don't really understand why that lets the compiler accept it
I appreciate that this lets it compile but I haven't guaranteed any information about that reference, only named it, but maybe this will enforce that the references in the iterator cannot go out of scope during this function call, if that is the case then I wish that was more obviously written in the lifetimes documentation.
nice explanation! i would recommend closing down your aperture and turning off continuous autofocus and autoexposure during the whiteboard scenes. that way both your face and the drawings can be in focus without any unexpected shifting during the shot
those sound like some really good tips, will try those, thank you!
I will try to use that model in the future let’s see how it goes.
Having some more examples would be helpful in those videos I think
Great video! For me as a Rust novice, this interpretation of lifetimes is very intuitive and useful! Looking forward to your next vid!
This is awesome, you have a way of breaking down complex things and using comparison that resonates with me. Looking forward to the borrow checker video.
The part about subtyping and outliving of lifetimes is so often overlooked an unexplained. The rewrite you start at 9:35 is, in my opinion, crucial and something that should have been done in The Book.
I have done basically the same code (with an attempt at subtyping and variance explanation) in several youtube comments on other "What are lifetime" videos which were lacking.
Awesome video man! I feel like I couldn't fully wrap my head around lifetimes as explained in the official docs, but after watching the video I now understand the other half of the picture. Thanks for sharing your knowledge!
This video explaining Rust's lifetimes is the best I've seen! Looking forward to more from you! Keep it up!
Outstanding! I really like your way of explaining lifetimes - it is FAR clearer than the usual explanations. Thank you!
I'm new to Rust, and ended up developing similar intuition regarding lifetimes. Thanks for validating it - can't wait for the follow up videos. Excellent stuff!
This one is gold. Give the perception to view the lifetime. Please make more video about lifetime please
Ok. The first three minutes of this video is exceptional!
I never coded anything in rust and I was only aware of lifetime/borrow checker existence but never guessed them to be this deep.
Your video is super good nonetheless since I think I got an intuitive understanding of the issue. Thanks! :)
In "example_1", I got that the lifetime of r starts when it is being assigned and also an implicit dependency is made to x's "memory region" to be valid (r is only valid if x is valid and usage outside of x being valid causes an error due to attempting to extend r's lifetime outside of x's valid scope)
And in "longest" example, I understood that the lifetime of 'out depends on both the lifetime of 's1 and 's2 (both shall have valid "memory region" for 'out to be valid; value of 'out can be anywhere between 's1 and 's2 but not outside of them -> usage of 'out outside of either of 's1 or 's2 valid scope will cause an error due to attempting to extend 'out lifetime where 's1 and/or 's2 no longer have valid "memory region")
One thing I don't get is why multiple lifetime parameters in functions (9:33 in video) can't also have their dependency deduced by the compiler instead of requiring explicit outlives constraints? It's more verbose and clearer for me as a reader that s1 and s2 should be allowed to have different lifetimes ('a on both makes me think their lifetimes are linked but it's not since it also has 'a on the output?). I would like to have multiple lifetime parameters but still have the compiler attempting to deduce the lifetime relations itself. Why not both? If I really want to actually tie the lifetime of s1 and s2 then I could use 'a on both but am forced to use something else (e.g. 'out) on return just to be different? And I also lose the compiler's help in deducing the lifetimes relations just because I want to tie s1 and s2's lifetimes? (for whatever reason) Wouldn't it be super simple for the compiler to do this deduction with one less lifetime even?
still waiting for part 2
Subtyping can be unintuitive, but it makes perfect sense for lifetime outlives semantics. Subtype can be naturally used in place of its supertype, much like a reference with outliving lifetime can be substituted in place of another one with lifetime it outlives (assuming covariance).
What I found missing about regions is that they still should carry notion of where in code they are alive, and determining what region a reference can point to would still be ultimately determined by liveness and usage. In that sense, this model seems like it would do the same but with an extra step, maybe.
yeah, the models are different sides of the same coin.
both liveness and aliasing matters.
since the official rust learning resources emphasize the liveness aspect, i decided to focus on the aliasing aspect for this video.
Very insightful! I thought I had a pretty good grasp on lifetimes before watching this video, but it still managed to get looking at them in a completely new way.
I already learnt what, how and why but this video explained it in more depth in a way that i realized that i didnt understood lifetimes at all.
Thanks a lot!
This makes it look like the lifetime is actually a part of the _value_ of the reference rather than the _type,_ which feels _weird._ In example 2, I'd expect it to only be valid if one of the two reassignments was a let-binding, shadowing the previous r with a new r with a different lifetime, meaning a different _type_ according to the model I expected. In most places in Rust, lifetimes are seen as type parameters (either to a struct or a reference) or type constraints, so the lifetime being attached to the value is non-intuitive unless you make references the only values with polymorphic types.
Then again, there are higher-rank trait bounds (for
interesting observation!
yeah, the "lifetime" is in fact a part of the value, cause that's a lot more flexible. or if you think about code from more of a "functional perspective", reassignment just creates a new `let` binding, that later uses bind to.
you can also make it make sense with functions, as those are "generic over" lifetimes. so you could imagine them being monomorphized for the specific lifetimes at the callsite (or you could imagine them being equivalent to their inlined body).
where it perhaps gets a little weird is in structs. although you could imagine those being replaced by their aggregate parts.
regarding traits, i think of them more as requirements for the type.
and even though T: Trait, T: 'a, 'a: 'b use the same syntax, i think of them as completely independent things.
thanks for the great explanation! damn got cliffhangered on the lifetimes of structs, hope you come back and make that someday
I find the second longest() function to be more intuitive then the first. I really don't understand how the first is translated to the second under the hood. Maybe that sub-typing and variance video will explain it
Newbie here, struggling (of course) with Rust concepts. I really like your alternate approach to understanding lifetimes. It helped me a lot.
To me the way to think about lifetime annotations, from a practical perspective, is that they are just a mechanism to tell the compiler the dependency between function arguments and returned value/s; the only case where is no such dependency is when a 'static reference is returned (i.e. the use of global variables as escape hatch). The use of annotations somewhere else is too artificial and unusual IMHO.
Also lifetime annotations are only used on references, so, in structs, the annotations are indirectly being applied to inner reference-type fields, possibly under some nested layers.
Thanks for the video. New to Rust as I'm trying to go through a codebase written in Rust.
The shear amount of syntax in Rust is overwhelming tbh. This cleared some bits
Thank you! This is the only video that actually makes me understand the concept
man, what a great video. subscribed! 100k subs with this level of quality content is inevitable.
sick ass video bro. i was thinking the same thing to with set theory and then you just bring it up. Really gave me a new perspective on it
very competent pedagogy, thank you.
This was actually very helpful, thank you
true
As a pretty senior developer coming to Rust, this explanation in terms of memory was really helpful to grok the concept. Nice examples, clearly annotated too!
1:22 as a C++ dev : you have to std::move it
here how the 'r can point to &x or &foo while x out of scope? 5:40
Game changing for my understanding of lifetimes. Thanks *so much*!
Really nice video, man, it helped improving my understanding of lifetimes. Thinking of memory regions made more sense to me.
Fantastic! First time lifetimes have really clicked for me! Thank you!
still waiting for the struct lifetimes vid!
i know :P
This makes more sense if you think of lifetimes as abstractions over memory allocations and frees attached to particular references, just like traits (interfaces) abstract over concrete types (a particular struct).
The difference with traits is that traits have no ordering because Rust is intentionally monomorphic, but lifetimes must have partial ordering because memory ops must be sequential to ensure memory safety. So that’s how you get covariance and contravariance.
Anyone who has worked with languages supporting subclassing like Java should be familiar with the concept of type variance found in lifetimes, once you start seeing lifetimes as part of the type signature.
Very nice alternative view on lifetime understanding, I'll try to apply it in my day to day Rust thinking and see how it helps! Bravo 👏
We need the borrow checking video too, love the way you explain
8:28, hold on, ch10 of the book says it's the *shorter* of the lifetimes of x and y
the example that makes me think this model is wrong goes like this:
say you replaced x and y with Strings so that they aren't `static lifetime
and if you tried to use l1 after making x go out of scope (and its memory was cleared), it'd still give you an error, even though it points to data owned by y, which would still be valid
modeling 'a as a set of memory that l1 can point at, which contains both 'x and 'y, seems to contradict this: that would imply 'a is valid when 'x OR 'y is still valid, but the book says it's while 'x AND 'y are still valid
which one is actually correct? I trust the book more than this
10:58 shouldnt it be 'b in 'a because as far as i know 'a: 'b reads as: 'a outlives or lives atleast as long as 'b
no, that's the thing, it's flipped!
with memory regions the thing on the left is actually the smaller thing, like you'd expect (or what i'd expect, anyway :D)
when something takes &'b T, you can pass &'a T,
if 'a lives at least as long as 'b,
or 'a is a sub-region/sub-set of 'b.
@@leddoo sorry if im misinterpreting (english is not my native language) but according to you this code:
fn some_fn
@@broom7294
right, when thinking about the lifetime, the thing on the left of 'a: 'b is greater.
but when thinking about regions of memory, the thing on the left is smaller.
your example doesn't compile, cause you're trying to return 'b, but 'b is not "in 'a" (what you're trying to return).
that's why you need 'b: 'a in this case.
then:
returning a is fine, cause it already has region 'a.
returning b is fine, cause it has region 'b, which is "in 'a".
This video is amazing, great job!
I can't help but read fn as 'f*ckin' whenever I look at Rust code
this is real as hell
thanks now i can never unsee this
@@merlangos my pleasure, that's fn awesome!
fn your(member: FamilyMember)
...yes, I'm a man child
@@carlosmspk fn hell(mate: YouGotMe) { println!("lmao"); }
Incredible explanation. You are genius man 🤯
Amazing stuff! May I know what tool are you using for presentation?
The problem with lifetimes is the amount of unrealistic simple examples it's used to explain it. The majority of these examples do not translate to real world use case structure.
Outstanding !! Please more videos centered around this covering various cases that might arise.
Also just wondering how would we apply this thinking in this example from the lifetimes chapter in the book:
fn longest y.len() {
x
} else {
y
}
}
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
}
I mean how we define what region 'a is pointing to?
Bro the borrow checker video please, I need more of this drug :D
great video, u have a great way of explaining concepts, keep it up! :)
'static isn't just leaked allocations/the .data section of the executable
a 'static lifetime constraint is also valid for any lifetime always guaranteed to outlive anything in the function/struct
Thank you! I just read the chapter in the book and was struggling to understand what the point of lifetimes are. I think they seriously need to redo their explanation.
I like to explain lifetimes as ranges on a timeline, where some ranges contain others. This way we can visualize the whole program as a “tree” spanning the duration of the program.
This video was so good that I feel like an inner trauma has been healed
some folks: "rust has no manual memory management"
'a: 'try, 'me
The region concept is confusing (an error?) at 8:40 and 10:23, The outer region means the shortest lifetime of a set of inner regions - calling this relationship using set and subset is the problem: the outer lifetime should be a subset of the inner lifetime. Both the box drawing and subset mean exactly the opposite: outer is a subset of any inner.
yes, a bit misleading
wonderful lecture!
thanks
i don’t know rust so i thought this was some cryptic philosophy take
Please make a video about variance!!
1:40 but they have? By your own admission later, the scopes do not change, but the lifetimes do.
I understand the motivating example, but the critique seems odd
Look, I'm all for being willing to drop preconcieved notions and recontextualize things, but all I'm saying is
"It all got a lot simpler once I started factoring in wave-particle duality"
Is not something programmers should ever be saying. I mean its not something *_anyone_* should be saying, but I'm convinced physists are masochists which, well, okay programmers are masochists too but it's a different *_brand_* of massochism.
How do you make the animations you have in your videos? I’m working with manim for math stuff, but your coding visuals are very slick
i have a pretty silly workflow right now :D
prepare code in neovim, take screenshots,
arrange in google slides, take screenshots,
put over audio in davinci resolve and add orange highlight rects.
the "memory to subset" animation was made with motion canvas.
Hello, world!
hello, oglo!
@@leddoo Hello!
Nice video ! and I'm not even learning rust just yet 🙃
anyway, what do you use to create the animation?
pls do other two videos now
Really good - I do wish you'd worked through the Struct example to round things out, but I guess that's now an exercise for the viewer :)
great little video, thanks!
shouldn't the input parameter outlive the output parameters since the output references the inputs?
output is returned to caller function and the input params only live till function's execution context is in call stack.
I appreciate the pun in the title.
“So let’s think about lifetimes by using the wave-particle duality, a concept that puzzled scientists for decades.” 😅
Thanks for the video
So you need a degree in quantum physics to understand lifetimes. Noted.
you did a good job on this one! what do you use to create / edit your videos?
Thank you, this video is a life-saver.
I always had this thought when considering the reversed subtype relation with respect to lifetime duration that there was some kind of "duality" here (in maths when you have subset relationship, you get the reversed relation when considering the "duals" of sets instead)
You put it very well with the electron analogy, memory region are like the "dual" of time when considering the lifetimes, this was the missing piece I was searching for.
Thank you for the enlightment 😁
that's awesome :D
imma have to look into duals of sets, that sounds interesting!
@@leddoo Just to be sure you won't be confused when looking at it, "duality" has a lot of meaning in math and there is no clear definition of "dual sets". You can define the dual of vector spaces in linear algebra or the dual of functions in convex analysis, they are related but not the same.
The main idea is that you can associate an action on others for each element of a space and the "dual space" is a set of elements that act on the set you are considered in a special way (for example the inner product of the associated element with any element of your set is always of the same sign). The more constrained the base set (smaller), the easier it is for an action to act "in a special way" (to keep the same sign) on it, so more elements are in the "dual". This is how subset's relationship ends up reversed in dual space.
Hope you enjoy the food for thought haha !
@@theopantamis9184 i'm not gonna pretend i understood the middle section yet, but i do appreciate it haha, thanks :D
so that basic example on wikipedia with the complement of a set makes sense.
i guess what's a bit weird is how with lifetimes, one perspective is based on sets of program points, and the other based on sets of borrows. so they have different "types". i wonder if there's some deeper reason for why the subsets are reversed in this case 🤔
@@leddoo sets' complement is a bit "too simple" and not very insightful 😅
This is closer to contravariance for function argument: the more arguments you allow as inputs of your function, the more constrained the function is if you want it to have a certain behavior (which is what you want as a dev).
I think we can put it that way: as you have shown, lifetimes are some kind of typed memory regions that must satisfy certain constraints (which are coded in the types relationships) but the usual way we look at it is through their dual: programs point are how you act on it, so we are often talking about functions on lifetimes, so on memory region.
That's why subtype relationships hold for the memory regions and is weirdly reversed when considering lifetimes as program points !
Obviously, this reasoning is not fully correct but reflects what I was thinking about 😁
Beautifully explained.
Amazing explanation and presentation. Keep 'em comin!
I don't use Rust. I find it great on paper but tedious in practice (although I haven't put much time into it). I still found this video useful and interesting.
Awesome work, great video!
I have an oftopic question. Do you mind sharing the font and color scheme you used for the code example. I find them very easy on the eyes and easy to read, something that I struggle with many other color schemes.
thanks!
color scheme is ayu mirage 👌
(i replied twice, cos "something went wrong")
@@leddooThank you very much! Do you mind also sharing the font name? It's really close to "Hack Font" but not quite and I really like it.
@@Schweppese should be SF mono (nerdfont version).
if not then Source Code Pro.
editor is neovim ;D
@@leddoo Thanks again!