lifetimekata
lifetimekata copied to clipboard
Claim of lifetime sizes in 00_welcome
Towards the end of the first chapter, once the 'variable and 'reference lifetimes has been established, there is the following statement:
We call a region of code where a variable exists a "lifetime". We can give lifetimes names using the syntax 'name. So if we call the variable's lifetime 'variable, and the reference's lifetime 'reference, we can then formally say that for any variable that references another variable, 'variable must be larger than 'reference.
The last part of this statement is confusing to me because it does not seem to be true for all cases.
Let's say in the code example, that the inner scope of 'variable is expanded to include the if let statement:
fn main() {
let mut my_reference: Option<&i32> = None;
// Starting a scope.
{
// my_variable created // \ \
let my_variable: i32 = 7; // | |
my_reference = Some(&my_variable); // | |- my_variable exists here. ('variable)
// At the end of the scope, `my_variable` is dropped // | |
drop(my_variable); // | |
// my variable destroyed // | /
// inner scope does not stop here }
// |
if let Some(reference) = my_reference { // |
println!("{}", reference); // |
} // /
} // inner scope now stops here
}
It is not true that 'variable is now larger than 'reference, because 'reference is declared earlier still.
So this is confusing to me. It would make more sense if it read:
we can then formally say that for any variable that references another variable, 'variable must live longer than 'reference.
Hey! So there are actually a couple things going on here, apologies if this is unstructured; and also apologies if I over-explain things (if nothing else, hopefully I can point other people here with similar issues).
First: the example you gave actually isn't quite doing what you think it is. This is because i32 is a type which is Copy.
Therefore, when you call drop(my_variable), you're actually copying my_variable, and basically calling drop(7).
A code example which gets around this issue is:
#[derive(Debug)]
struct NotCopy {
i: i32
}
fn main() {
let mut my_reference: Option<&NotCopy> = None;
// Starting a scope.
{
// my_variable created // \ \
let my_variable: NotCopy = NotCopy { i : 7}; // | |
my_reference = Some(&my_variable); // | |- my_variable exists here. ('variable)
// At the end of the scope, `my_variable` is dropped // | |
drop(my_variable); // | |
// my variable destroyed // | /
// inner scope does not stop here }
// |
if let Some(reference) = my_reference { // |
println!("{reference:?}"); // |
} // /
} // inner scope now stops here
}
But what you'll see in this example is that we do run into an issue:
error[E0505]: cannot move out of `my_variable` because it is borrowed
--> src/main.rs:15:14
|
13 | my_reference = Some(&my_variable); // | |- my_variable exists here. ('variable)
| ------------ borrow of `my_variable` occurs here
14 | // At the end of the scope, `my_variable` is dropped // | |
15 | drop(my_variable); // | |
| ^^^^^^^^^^^ move out of `my_variable` occurs here
...
19 | if let Some(reference) = my_reference { // |
| ------------ borrow later used here
The error here is with the drop, because that's the first point where rust can't figure out what to do: before that point, it's happy to give you a borrow of my_variable, but at that point it's either got to move my_variable, or let you keep that borrow (and since it can't do both, it's an error!).
I do think that the wording here could be improved, and I'm gonna have a think about how to do that (suggestions welcome) but before I do, I'd like to know if this explanation helped, or if there's something you're still confused about. That'll help me figure out what the best wording actually is!
Thanks for opening an issue!
Hey @tfpk , thanks for responding promptly!
I think I follow what you're getting at with drop but it's raising more questions than it answers.
So reading 00_example, I chucked this into the playground and it works. ie, putting println! inside the inner scope, but after the drop prints 7 (https://play.rust-lang.org/?version=beta&mode=debug&edition=2021&gist=62de6293aff89f91d15e7d57b9446bce).
So now I'm confused about several things.
- Why does my_reference work, when it's referencing my_variable and my_variable is dropped on ln 15 and my_reference is used on ln 19. Is that because once dropped, the Copy trait implicitly does a copy of my_variable and keeps that variable around, what is it's lifetime? Or is it because my_reference just refers to &7 which permanently exists and has a
'staticlifetime? - How does
drop(my_variable)differ from closing the scope{ }because in the playround, if I was to put the println! outside the scope, then it errors as expected, the compiler calls out that my_variable is no longer in scope at the time my_reference is being used in the println!, however, if theprintln!is inside the scope{ }but after droppingmy_variable, it still works. so there's some differences there. - My OP question still remains, in both cases, the scope of my_variable is smaller than my_reference, that is because my_reference's scope is from the start of
main()to the end, so perhaps this is rust jargon that I don't get yet, but my understanding is that the lifetime of'variablehas to end later than the lifetime of the things that reference it, ie `'my_reference'
I think there's two things here, a simpler explaination of lifetime and an accidental complexity introduced by this example. And we're now talking about both!
Sorry to pile on the confusion, let's try get it straightened out...
- The reason for this is because calling
drop(my_variable)(wheremy_variableisCopy) is identical todrop(my_variable.clone()). That's what it means for a variable to be copy, and hopefully here you can see that it would mean that thedropfunction basically has no effect. - If a variable exists within a scope, it cannot be accessed from outside that scope. Trying to use its name is an error because outside that scope, the name simply does not exist. Calling
dropdoes not cause the name to stop working, it simply moves the value. Drop is literally the functionpub fn drop<T>(_x: T) { }(i.e. it moves the value, then does nothing with it). This is the same as any moved value.
To help with 1 and 2, the reading the documentation of the drop function might help!
- Yeah I think I somewhat dodged this question, apologies!. The important thing here is this: the lifetime of a reference is always smaller than the lifetime of the variable it refers to. Here's an example of what I think you're getting at:
#[derive(Debug)]
struct NotCopy {
i: i32
}
fn main() {
let mut my_reference: Option<&NotCopy> = None; // lifetime of my_reference starts
let my_value: NotCopy = NotCopy { i: 8 }; // lifetime of my_value starts
my_reference = Some(&my_value);
if let Some(val) = my_reference {
println!("{val:?}");
}
}
Which seems to disprove my comment made in section 0. The trick here is that there are two seperate lifetimes which are being conflated here: the lifetime of my_reference (the variable), and the lifetime of &my_value (which is being stored inside my_reference. The documentation kind of confuses the issue here (but I'm gonna have to think about how to fix it, because it's hard to say non-confusingly).
It is clear that the variables my_reference and my_value do not necessarily have to share a lifetime on their own. The trick is that &my_value changes things: clearly, the reference &my_value must have a smaller lifetime than my_value (you can't get a reference to something before it exists, and Rust won't let you keep a reference around after the value being referenced is gone).
Okay I've had this checked; pretty sure it's all reasonable.
I've tried to improve the wording in #19, but it's a first draft which I'll have to revise again, I suspect.