pub unsafe trait Yokeable<'a>: 'static {
type Output: 'a;
// Required methods
fn transform(&'a self) -> &'a Self::Output;
fn transform_owned(self) -> Self::Output;
unsafe fn make(from: Self::Output) -> Self;
fn transform_mut<F>(&'a mut self, f: F)
where F: 'static + for<'b> FnOnce(&'b mut Self::Output);
}
Expand description
The Yokeable<'a>
trait is implemented on the 'static
version of any zero-copy type; for
example, Cow<'static, T>
implements Yokeable<'a>
(for all 'a
).
One can use
Yokeable::Output
on this trait to obtain the “lifetime’d” value of the Cow<'static, T>
,
e.g. <Cow<'static, T> as Yokeable<'a>'>::Output
is Cow<'a, T>
.
A Yokeable
type is essentially one with a covariant lifetime parameter,
matched to the parameter in the trait definition. The trait allows one to cast
the covariant lifetime to and from 'static
.
Most of the time, if you need to implement Yokeable
, you should be able to use the safe
#[derive(Yokeable)]
custom derive.
While Rust does not yet have GAT syntax, for the purpose of this documentation
we shall refer to “Self
with a lifetime 'a
” with the syntax Self<'a>
.
Self<‘static> is a stand-in for the HKT Self<’_>: lifetime -> type.
With this terminology, Yokeable
exposes ways to cast between Self<'static>
and Self<'a>
generically.
This is useful for turning covariant lifetimes to dynamic lifetimes, where 'static
is
used as a way to “erase” the lifetime.
§Safety
This trait is safe to implement on types with a covariant lifetime parameter, i.e. one where
Self::transform()
’s body can simply be { self }
. This will occur when the lifetime
parameter is used within references, but not in the arguments of function pointers or in mutable
positions (either in &mut
or via interior mutability)
This trait must be implemented on the 'static
version of such a type, e.g. one should
implement Yokeable<'a>
(for all 'a
) on Cow<'static, T>
.
This trait is also safe to implement on types that do not borrow memory.
There are further constraints on implementation safety on individual methods.
§Trait bounds
Compiler bug #85636 makes it tricky to add
trait bounds on Yokeable::Output
. For more information and for workarounds, see
crate::trait_hack
.
§Implementation example
Implementing this trait manually is unsafe. Where possible, you should use the safe
#[derive(Yokeable)]
custom derive instead. We include an example
in case you have your own zero-copy abstractions you wish to make yokeable.
struct Bar<'a> {
numbers: Cow<'a, [u8]>,
string: Cow<'a, str>,
owned: Vec<u8>,
}
unsafe impl<'a> Yokeable<'a> for Bar<'static> {
type Output = Bar<'a>;
fn transform(&'a self) -> &'a Bar<'a> {
// covariant lifetime cast, can be done safely
self
}
fn transform_owned(self) -> Bar<'a> {
// covariant lifetime cast, can be done safely
self
}
unsafe fn make(from: Bar<'a>) -> Self {
// We're just doing mem::transmute() here, however Rust is
// not smart enough to realize that Bar<'a> and Bar<'static> are of
// the same size, so instead we use transmute_copy
// This assert will be optimized out, but is included for additional
// peace of mind as we are using transmute_copy
debug_assert!(mem::size_of::<Bar<'a>>() == mem::size_of::<Self>());
let ptr: *const Self = (&from as *const Self::Output).cast();
mem::forget(from);
ptr::read(ptr)
}
fn transform_mut<F>(&'a mut self, f: F)
where
F: 'static + FnOnce(&'a mut Self::Output),
{
unsafe { f(mem::transmute::<&mut Self, &mut Self::Output>(self)) }
}
}
Required Associated Types§
Required Methods§
sourcefn transform_owned(self) -> Self::Output
fn transform_owned(self) -> Self::Output
sourcefn transform_mut<F>(&'a mut self, f: F)
fn transform_mut<F>(&'a mut self, f: F)
This method must cast self
between &'a mut Self<'static>
and &'a mut Self<'a>
,
and pass it to f
.
§Implementation safety
A safe implementation of this method must be equivalent to a pointer cast/transmute between
&mut Self<'a>
and &mut Self<'static>
being passed to f
§Why is this safe?
Typically covariant lifetimes become invariant when hidden behind an &mut
,
which is why the implementation of this method cannot just be f(self)
.
The reason behind this is that while reading a covariant lifetime that has been cast to a shorter
one is always safe (this is roughly the definition of a covariant lifetime), writing
may not necessarily be safe since you could write a smaller reference to it. For example,
the following code is unsound because it manages to stuff a 'a
lifetime into a Cow<'static>
struct Foo {
str: String,
cow: Cow<'static, str>,
}
fn unsound<'a>(foo: &'a mut Foo) {
let a: &str = &foo.str;
foo.cow.transform_mut(|cow| *cow = Cow::Borrowed(a));
}
However, this code will not compile because Yokeable::transform_mut()
requires F: 'static
.
This enforces that while F
may mutate Self<'a>
, it can only mutate it in a way that does
not insert additional references. For example, F
may call to_owned()
on a Cow
and mutate it,
but it cannot insert a new borrowed reference because it has nowhere to borrow from –
f
does not contain any borrowed references, and while we give it Self<'a>
(which contains borrowed
data), that borrowed data is known to be valid
Note that the for<'b>
is also necessary, otherwise the following code would compile:
// also safely implements Yokeable<'a>
struct Bar<'a> {
num: u8,
cow: Cow<'a, u8>,
}
fn unsound<'a>(bar: &'a mut Bar<'static>) {
bar.transform_mut(move |bar| bar.cow = Cow::Borrowed(&bar.num));
}
which is unsound because bar
could be moved later, and we do not want to be able to
self-insert references to it.
The for<'b>
enforces this by stopping the author of the closure from matching up the input
&'b Self::Output
lifetime with 'a
and borrowing directly from it.
Thus the only types of mutations allowed are ones that move around already-borrowed data, or introduce new owned data:
struct Foo {
str: String,
cow: Cow<'static, str>,
}
fn sound<'a>(foo: &'a mut Foo) {
foo.cow.transform_mut(move |cow| cow.to_mut().push('a'));
}
More formally, a reference to an object that f
assigns to a reference
in Self<’a> could be obtained from:
- a local variable: the compiler rejects the assignment because ’a certainly outlives local variables in f.
- a field in its argument: because of the for<’b> bound, the call to
f
must be valid for a particular ’b that is strictly shorter than ’a. Thus, the compiler rejects the assignment. - a reference field in Self<’a>: this does not extend the set of non-static lifetimes reachable from Self<’a>, so this is fine.
- one of f’s captures: since F: ’static, the resulting reference must refer to ’static data.
- a static or thread_local variable: ditto.