Lifetime-dynamic smart pointers.
In short, this crate is useful when you want
to deal with objects annotated with lifetime.
More specifically, when you create weak
pointers [LifeWeak] to these objects, so that:
- The pointee objects live in their own lifetimes, respecting the borrow checker.
- With the weak pointer, the caller may operate on pointee objects that are still alive, and ignore those who have gone out of their own lifetimes.
- The lifetime parameters of pointee type
are masked on the weak pointer
(e.g.
Obj<'a> -> LifeWeak<Obj<'static>>) to annotate lifetime checking as dynamic, so that the weak pointers can be held as fields or stored in containers.
This crate works under these premises:
- If a lifetime
'ais alive before calling a function, then it will at least be alive until returning from this function. - The pointee
Obj<'a>is (reducible to be) parameterized by a single lifetime parameter'aand covariant over lifetime'a. That is, if'bis a lifetime completely contained in'a, thenObj<'a>can be used in place ofObj<'b>. - When operating on the pointee object
from [
LifeWeak<Obj<'static>>], either no [LifeRc<Obj<'a>>] is dropped, or the [Drop::drop] behavior ofObj<'a>is completely irrelevant to its lifetime parameter'a.
These premises can be held at most of cases. But if any of these premises is broken, then the behavior of this crate is undefined.
Why [Rc] and [Weak] don't suffice?
To illustrate the problem, imagine a scenario where lifetime annotated object must be used:
Let's assume the attention of writing
so is: While some of these Str<'_>
object may go out of scope, we just
care about the ones that are still
alive, and print them out.
When you plug the code into rust compiler,
the borrow checker will soon complain
about that a.as_str() borrows a
while it does not live long enough.
However, the intention of this code
is to allow a to drop, in that case,
the weak pointer Rc::downgrade(&a_rc)
should dereference to None.
While the error originates from the
borrow checker, I would say it's rather
a type error. We know lifetimes are built
into rust types, and in the code above,
the borrow checker will attempt to infer
the ellided lifetime of Vec<Weak<Str<'_>>>.
However, it's impossible to infer
such a correct lifetime, since it will
be otherwise self-contradictory:
- If the lifetime is as long as the block
where
ais in, let it be'a, thenweaks: Vec<Weak<Str<'a>>>continues to live after exiting the blockais in, which violates the rule of an object cannot outlives its lifetime annotation. - If the lifetime is as long as the one
where
weaksandbis in, let it be'b, thenRc::downgrade(&a_rc)is not assignable toWeak<Str<'b>>, as it does not live long enough to be'b.
In fact, although we won't be able to
access a dead Str<'_> with Weak<Str<'_>>,
there's no way to "slack" the lifetime on
Weak<Str<'_>>, since the standard library
has a good reason to adhere the lifetime:
Let there be a weak pointer
weak: Weak<Str<'c>> from rc: Rc<Str<'c>>,
if we upgrade it back to Some(Rc<Str<'c>>)
by weak.upgrade() successfully, then
such an upgraded pointer truly holds an
object of Str<'c>, which must not outlive
'c. Therefore, as long as we are able to
recover a Weak<Str<'_>> back to a
Rc<Str<'_>>, the lifetime is mandatory.
Our method
Generally speaking, we diverge from the
standard library by forbidding the upgrade
of a weak pointer [LifeWeak]. Instead,
the caller pass a callback function to
[LifeWeak::with], then the weak pointer
checks if the pointee is still alive,
"fix" the lifetime of the reference to
the pointee, to a reasonably short one,
and finally pass the reference into
the callback to perform the operation.
Let the pointee type be t: Obj<'a>,
and we use pseudo rust code to
illustrate the idea.
First, the user create
reference counting [LifeRc<Obj<'a>>]
out of t. The reference-counting
pointer is annotated with the lifetime
'a and respect the borrow checker:
Then, the user downgrade [LifeRc<Obj<'a>>]
to weak pointer [LifeWeak<Obj<'static>>],
and send it to the weak consumer of t.
The lifetime 'static means we want
to detach the lifetime from 'a and
check it at runtime:
Finally, the weak consumer of t pass a
callback to [LifeWeak<Obj<'static>>::with],
to perform acton on t:
Let's give a closer look at why
[LifeWeak<Obj<'static>>::with] works.
Recall that we made some premises,
they are applicable now:
-
In the [
LifeWeak<Obj<'static>>::with] function, if we are able to upgrade the internal weak pointer, then at least one reference counting [Rc<Obj<'a>>] is still alive, since a [Rc<Obj<'a>>] cannot outlives'a, lifetime'amust have been alive before entering [LifeWeak<Obj<'static>>::with], By premise #1,'ais alive during the whole execution of the [LifeWeak<Obj<'static>>::with] function. -
Therefore lifetime
'bis completely contained in'a. In the function [LifeWeak<Obj<'static>>::with], we reconstruct the lifetime ofObj<'a>intoObj<'b>, and by premise #2 thatObj<'a>is covariant, plus the variance rule of an immutable pointer, we can use&*rc: &'b Obj<'a>in the place of&'b Obj<'b>. Thustis well-formed. -
By premise #3, either we won't drop any [
LifeRc<Obj<'a>>] during the execution ofcallback, so that we don't ever need to care about callingObj<'static>::drop; or the [Drop::drop] ofObj<'a>is irrelevant to its lifetime'a, and thus callingObj<'static>::dropandObj<'a>::dropare equivalent.In either way,Rc<Obj<'a>>will be maintained correctly.
With our method, we can fix the previous example into: