I am trying to get a better understanding of Rust lifetimes.
struct Name<'a> {
arg: &'a u8,
}
impl<'a> Name<'a> {
fn new1(arg: &'a u8) -> Name<'a> {
Name { arg }
}
fn new2<'b>(arg: &'b u8) -> Name<'b> {
Name { arg }
}
}
Is there any difference between functions new1
and new2
? I am assuming it would matter if arg
was &self
? Is there any case where new2
implementation preferred or the other way?
These two methods end up being exactly the same, but it's worth learning why. Along the way, we'll learn about lifetime coercion
One lifetime can be coerced into another lifetime if that second lifetime is shorter than (or rather, is contained by) the first. This is usually notated 'a: 'b
, to mean that the lifetime 'a
entirely encloses the lifetime 'b
. The usual terminology is that 'a
outlives 'b
. The reasoning for this coercion is that you can always make a lifetime shorter if you need to. If a reference is valid during some lifetime, then it's also valid during any shorter lifetime contained in the longer lifetime.
So with that in mind, what sort of arguments can new1
and new2
take? We have a fixed lifetime 'a
since the whole implementation is generic in that lifetime. However, new1
can not only take &'a u8
, but any &'b u8
if 'b
can be coerced to 'a
. That is, 'b: 'a
and 'b
is longer than 'a
.
new2
is slightly different, but it ends up having the same effect. The method is generic in the lifetime 'b
and can take any &'c u8
if 'c: 'b
. new2
is still technically generic in 'a
, but since it doesn't use 'a
at all, it can be ignored. That said, ignoring a generic parameter is confusing (why have the parameter at all?), so it would probably be best to use new1
instead.
Another reason to prefer new1
over new2
is that it fits better with Self
. If we try to change the outputs to Self
impl<'a> Name<'a> {
fn new1(arg: &'a u8) -> Self {
Name { arg }
}
fn new2<'b>(arg: &'b u8) -> Self {
Name { arg }
}
}
the compiler complains. Why? Now the outputs have to be Name<'a>
and in new2
, we're returning Name<'b>
. This isn't coercible to Name<'a>
unless 'b: 'a
, so we have to add that as a bound on 'b
:
impl<'a> Name<'a> {
fn new1(arg: &'a u8) -> Self {
Name { arg }
}
fn new2<'b: 'a>(arg: &'b u8) -> Self {
Name { arg }
}
}
(playground link)
In this case, it's pretty clear that new1
is superior since it doesn't even need that second lifetime, but still allows exactly the same inputs.
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