There was some discussion of this in IRC yesterday, which left me feeling vaguely dissatisfied.
The question was:
How do you define a lifetime on a struct to restrict its contents to only things that live as long as 'itself'.
i.e. a 'self
sort of thing.
My initial reaction was: you can't.
If you create a struct Foo<'a>
, the lifetime associated with it is inferred from the references it contains; unless the struct contains a reference to itself (impossible), you can't have this sort of 'self
lifetime.
There was a bunch of chatter about it, and I ended up writing this playground as a result:
#[derive(Debug)]
struct Bar;
#[derive(Debug)]
struct Foo<'a> {
a:&'a Bar,
b:&'a Bar
}
fn factory<'a>(v1:&'a Bar, v2: &'a Bar) -> Foo<'a> {
return Foo {
a: v1,
b: v2
};
}
fn main() { // <---- Let's call this block lifetime 'one
let a = Bar;
let c = &a; // <-- C has lifetime 'one
{ // <------------ Let's call this block lifetime 'two
let b = Bar;
let mut foo1 = factory(c, c);
foo1.b = &b;
let mut foo2 = factory(&b, &b);
foo2.a = &a;
println!("{:?}", foo1);
println!("{:?}", foo2);
}
}
However, I'm now more confused rather than less.
So, in a strict sense in the above:
c
has 'one
&b
has 'two
'static
> 'one
> 'two
(that is, 'two
is bounded by 'one
). foo1
has 'one
foo2
has 'two
Now, my confusion:
Foo<'a>
indicates that 'a
is the minimum lifetime bound that can be contained by the instance of Foo
.
Since 'one
> 'two
, foo2
should be able contain a &'one a
; this works.
Since 'two
> 'one
, foo1
should not be able to contain &'two b
; however, this works.
Why?
It would appear my confusion results from one of two misconceptions; either:
The instance of foo1
is in fact Foo<'two>
, not Foo<'one>
.
I don't understand why this would be the case, since it is manufactured in factory<'a>
where <'a>
is the lifetime of c
; which is 'one
, not 'two
. There's absolutely no way c
can be &'two
in the example above. The lifetime 'two
is not available in the function factory where Foo
is created.
2) Struct lifetimes don't work how I understand them to work; i.e. a lifetime of 'a
on a Foo
instance can somehow change between after the instance is created (e.g. on a move?)
...but I don't know which one.
Lifetime parameters on references are covariant: they can be substituted with a shorter lifetime when necessary.
Basically, you've got it the wrong way around. When you have a &'one Bar
, you cannot assign a reference to a value with a shorter lifetime (such as 'two
here), otherwise the reference would be dangling when execution leaves the 'two
scope. However, when you have a &'two Bar
, you can assign a reference to a value with a longer lifetime (such as 'one
and 'static
), because the reference will go out of scope before the referent does.
Why does your program compile? The compiler doesn't only use information from the calls to factory
to select the appropriate lifetime; it also uses information from the assignments. &a
has type &'one Bar
and &b
has type &'two Bar
. Because 'two
starts after 'one
and ends before 'one
, the compiler can coerce a &'one Bar
to a &'two Bar
. In object-oriented terms, a &'one Bar
is a &'two Bar
(&'one Bar
is a subtype of &'two Bar
). Just like how in Java, you can pass a String
as an argument to a function that expects an Object
. The subtyping relation for classes in Java is that child classes are subtypes of their parents, but the subtyping relation for lifetimes is that longer lifetimes are subtypes of shorter ones.
This means that we've found a common type for &a
and &b
: &'two Bar
. Therefore, the compiler infers 'two
for 'a
in the calls to factory
.
Note that the type of foo2
doesn't change at the assignment; the type of a value is always static.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With