Struct Yoke
struct Yoke<Y: for<'a> Yokeable<'a>, C> { ... }
A Cow-like borrowed object "yoked" to its backing data.
This allows things like zero copy deserialized data to carry around shared references to their backing buffer, by "erasing" their static lifetime and turning it into a dynamically managed one.
Y (the Yokeable) is the object containing the references,
and will typically be of the form Foo<'static>. The 'static is
not the actual lifetime of the data, rather it is a convenient way to mark the
erased lifetime and make it dynamic.
C is the "cart", which Y may contain references to. After the yoke is constructed,
the cart serves little purpose except to guarantee that Y's references remain valid
for as long as the yoke remains in memory (by calling the destructor at the appropriate moment).
The primary constructor for Yoke is [Yoke::attach_to_cart()]. Several variants of that
constructor are provided to serve numerous types of call sites and Yoke signatures.
The key behind this type is [Yoke::get()], where calling [.get()][Yoke::get] on a type like
Yoke<Cow<'static, str>, _> will get you a short-lived &'a Cow<'a, str>, restricted to the
lifetime of the borrow used during .get(). This is entirely safe since the Cow borrows from
the cart type C, which cannot be interfered with as long as the Yoke is borrowed by .get (). .get() protects access by essentially reifying the erased lifetime to a safe local one
when necessary.
Furthermore, there are various [.map_project()][Yoke::map_project] methods that allow turning a Yoke
into another Yoke containing a different type that may contain elements of the original yoked
value. See the [Yoke::map_project()] docs for more details.
In general, C is a concrete type, but it is also possible for it to be a trait object.
Example
For example, we can use this to store zero-copy deserialized data in a cache:
# use Yoke;
# use Rc;
# use Cow;
#
let yoke = load_object;
assert_eq!;
assert!;
Implementations
impl<Y, C> Yoke<Y, C>
fn attach_to_zero_copy_cart(cart: C) -> SelfConstruct a
Yoke<Y, C>from a cart implementingStableDerefby zero-copy cloning the cart toYand then yokeing that object to the cart.The type
Ymust implementZeroFrom<C::Target>. This trait is auto-implemented on many common types and can be custom implemented or derived in order to make it easier to construct aYoke.Example
Attach to a cart:
use Cow; use Yoke; let yoke = attach_to_zero_copy_cart; assert_eq!;
impl<Y: for<'a> Yokeable<'a>> Yoke<Y, ()>
fn new_always_owned(yokeable: Y) -> SelfConstruct a new
Yokefrom static data. There will be no references tocarthere sinceYokeables are'static, this is good for e.g. constructing fully ownedYokes with no internal borrowing.This is similar to [
Yoke::new_owned()] but it does not allow you to mix theYokewith borrowed data. This is primarily useful for usingYokein generic scenarios.Example
# use Yoke; # use Cow; let owned: = "hello".to_owned.into; // this yoke can be intermingled with actually-borrowed Yokes let yoke: = new_always_owned; assert_eq!;fn into_yokeable(self: Self) -> YObtain the yokeable out of a
Yoke<Y, ()>For most
Yoketypes this would be unsafe but it's fine forYoke<Y, ()>since there are no actual internal references
impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, Option<C>>
const fn new_owned(yokeable: Y) -> SelfConstruct a new
Yokefrom static data. There will be no references tocarthere sinceYokeables are'static, this is good for e.g. constructing fully ownedYokes with no internal borrowing.This can be paired with [
Yoke:: wrap_cart_in_option()] to mix owned and borrowed data.If you do not wish to pair this with borrowed data, [
Yoke::new_always_owned()] can be used to get aYokeAPI on always-owned data.Example
# use Yoke; # use Cow; # use Rc; let owned: = "hello".to_owned.into; // this yoke can be intermingled with actually-borrowed Yokes let yoke: = new_owned; assert_eq!;fn try_into_yokeable(self: Self) -> Result<Y, Self>Obtain the yokeable out of a
Yoke<Y, Option<C>>if possible.If the cart is
None, this returnsOk, but if the cart isSome, this returnsselfas an error.
impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C>
fn map_project<P, F>(self: Self, f: F) -> Yoke<P, C> where P: for<'a> Yokeable<'a>, F: for<'a> FnOnce(<Y as Yokeable<'a>>::Output, PhantomData<&'a ()>) -> <P as Yokeable<'a>>::OutputAllows one to "project" a yoke to perform a transformation on the data, potentially looking at a subfield, and producing a new yoke. This will move cart, and the provided transformation is only allowed to use data known to be borrowed from the cart.
The callback takes an additional
PhantomData<&()>parameter to anchor lifetimes (see #86702) This parameter should just be ignored in the callback.This can be used, for example, to transform data from one format to another:
# use Rc; # use Yoke; #This can also be used to create a yoke for a subfield
# use ; # use mem; # use Rc; # // also safely implements Yokeable<'a> # # unsafefn map_project_cloned<'this, P, F>(self: &'this Self, f: F) -> Yoke<P, C> where P: for<'a> Yokeable<'a>, C: CloneableCart, F: for<'a> FnOnce(&'this <Y as Yokeable<'a>>::Output, PhantomData<&'a ()>) -> <P as Yokeable<'a>>::OutputThis is similar to
Yoke::map_project, however it does not moveSelfand instead clones the cart (only if the cart is aCloneableCart)This is a bit more efficient than cloning the
Yokeand then callingYoke::map_projectbecause then it will not clone fields that are going to be discarded.fn try_map_project<P, F, E>(self: Self, f: F) -> Result<Yoke<P, C>, E> where P: for<'a> Yokeable<'a>, F: for<'a> FnOnce(<Y as Yokeable<'a>>::Output, PhantomData<&'a ()>) -> Result<<P as Yokeable<'a>>::Output, E>This is similar to
Yoke::map_project, however it can also bubble up an error from the callback.# use Rc; # use Yoke; # use ; #This can also be used to create a yoke for a subfield
# use ; # use mem; # use Rc; # use ; # // also safely implements Yokeable<'a> # # unsafefn try_map_project_cloned<'this, P, F, E>(self: &'this Self, f: F) -> Result<Yoke<P, C>, E> where P: for<'a> Yokeable<'a>, C: CloneableCart, F: for<'a> FnOnce(&'this <Y as Yokeable<'a>>::Output, PhantomData<&'a ()>) -> Result<<P as Yokeable<'a>>::Output, E>This is similar to
Yoke::try_map_project, however it does not moveSelfand instead clones the cart (only if the cart is aCloneableCart)This is a bit more efficient than cloning the
Yokeand then callingYoke::map_projectbecause then it will not clone fields that are going to be discarded.fn map_project_with_explicit_capture<P, T>(self: Self, capture: T, f: for<'a> fn(_: <Y as Yokeable<'a>>::Output, capture: T, _: PhantomData<&'a ()>) -> <P as Yokeable<'a>>::Output) -> Yoke<P, C> where P: for<'a> Yokeable<'a>This is similar to
Yoke::map_project, but it works around older versions of Rust not being able to useFnOnceby using an explicit capture input. See #1061.See the docs of
Yoke::map_projectfor how this works.fn map_project_cloned_with_explicit_capture<'this, P, T>(self: &'this Self, capture: T, f: for<'a> fn(_: &'this <Y as Yokeable<'a>>::Output, capture: T, _: PhantomData<&'a ()>) -> <P as Yokeable<'a>>::Output) -> Yoke<P, C> where P: for<'a> Yokeable<'a>, C: CloneableCartThis is similar to
Yoke::map_project_cloned, but it works around older versions of Rust not being able to useFnOnceby using an explicit capture input. See #1061.See the docs of
Yoke::map_project_clonedfor how this works.fn try_map_project_with_explicit_capture<P, T, E>(self: Self, capture: T, f: for<'a> fn(_: <Y as Yokeable<'a>>::Output, capture: T, _: PhantomData<&'a ()>) -> Result<<P as Yokeable<'a>>::Output, E>) -> Result<Yoke<P, C>, E> where P: for<'a> Yokeable<'a>This is similar to
Yoke::try_map_project, but it works around older versions of Rust not being able to useFnOnceby using an explicit capture input. See #1061.See the docs of
Yoke::try_map_projectfor how this works.fn try_map_project_cloned_with_explicit_capture<'this, P, T, E>(self: &'this Self, capture: T, f: for<'a> fn(_: &'this <Y as Yokeable<'a>>::Output, capture: T, _: PhantomData<&'a ()>) -> Result<<P as Yokeable<'a>>::Output, E>) -> Result<Yoke<P, C>, E> where P: for<'a> Yokeable<'a>, C: CloneableCartThis is similar to
Yoke::try_map_project_cloned, but it works around older versions of Rust not being able to useFnOnceby using an explicit capture input. See #1061.See the docs of
Yoke::try_map_project_clonedfor how this works.
impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C>
fn wrap_cart_in_either_a<B>(self: Self) -> Yoke<Y, EitherCart<C, B>>Helper function allowing one to wrap the cart type
Cin anEitherCart.This function wraps the cart into the
Avariant. To wrap it into theBvariant, use [Self::wrap_cart_in_either_b()].For an example, see
EitherCart.fn wrap_cart_in_either_b<A>(self: Self) -> Yoke<Y, EitherCart<A, C>>Helper function allowing one to wrap the cart type
Cin anEitherCart.This function wraps the cart into the
Bvariant. To wrap it into theAvariant, use [Self::wrap_cart_in_either_a()].For an example, see
EitherCart.
impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C>
fn wrap_cart_in_box(self: Self) -> Yoke<Y, Box<C>>Helper function allowing one to wrap the cart type
Cin aBox<T>. Can be paired with [Yoke::erase_box_cart()]✨ Enabled with the
allocCargo feature.fn wrap_cart_in_rc(self: Self) -> Yoke<Y, Rc<C>>Helper function allowing one to wrap the cart type
Cin anRc<T>. Can be paired with [Yoke::erase_rc_cart()], or generally used to make theYokecloneable.✨ Enabled with the
allocCargo feature.fn wrap_cart_in_arc(self: Self) -> Yoke<Y, Arc<C>>Helper function allowing one to wrap the cart type
Cin anRc<T>. Can be paired with [Yoke::erase_arc_cart()], or generally used to make theYokecloneable.✨ Enabled with the
allocCargo feature.
impl<Y: for<'a> Yokeable<'a>, C> Yoke<Y, C>
fn get<'a>(self: &'a Self) -> &'a <Y as Yokeable<'a>>::OutputObtain a valid reference to the yokeable data
This essentially transforms the lifetime of the internal yokeable data to be valid. For example, if you're working with a
Yoke<Cow<'static, T>, C>, this will return an&'a Cow<'a, T>Example
# use Yoke; # use Rc; # use Cow; # # # // load_object() defined in the example at the top of this page let yoke: = load_object; assert_eq!;fn backing_cart(self: &Self) -> &CGet a reference to the backing cart.
This can be useful when building caches, etc. However, if you plan to store the cart separately from the yoke, read the note of caution below in
Yoke::into_backing_cart.fn into_backing_cart(self: Self) -> CGet the backing cart by value, dropping the yokeable object.
Caution: Calling this method could cause information saved in the yokeable object but not the cart to be lost. Use this method only if the yokeable object cannot contain its own information.
Example
Good example: the yokeable object is only a reference, so no information can be lost.
use Yoke; let local_data = "foo".to_owned; let yoke = attach_to_zero_copy_cart; assert_eq!; // Get back the cart let cart = yoke.into_backing_cart; assert_eq!;Bad example: information specified in
.with_mut()is lost.use Cow; use Yoke; let local_data = "foo".to_owned; let mut yoke = attach_to_zero_copy_cart; assert_eq!; // Override data in the cart yoke.with_mut; assert_eq!; // Get back the cart let cart = yoke.into_backing_cart; assert_eq!; // WHOOPS!unsafe fn replace_cart<C2, impl FnOnce(C) -> C2: FnOnce(C) -> C2>(self: Self, f: impl FnOnce(C) -> C2) -> Yoke<Y, C2>Unsafe function for replacing the cart with another
This can be used for type-erasing the cart, for example.
Safety
-
f()must not panic -
References from the yokeable
Yshould still be valid for the lifetime of the returned cart typeC.For the purpose of determining this,
Yokeguarantees that references from the YokeableYinto the cartCwill never be references into its stack data, only heap data protected byStableDeref. This does not necessarily mean thatCimplementsStableDeref, rather that any data referenced byYmust be accessed through aStableDerefimpl on somethingCowns.Concretely, this means that if
C = Option<Rc<T>>,Ymay contain references to theTbut not anything else. -
Lifetimes inside C must not be lengthened, even if they are themselves contravariant. I.e., if C contains an
fn(&'a u8), it cannot be replaced with `fn(&'static u8), even though that is typically safe.
Typically, this means implementing
fas something which wraps the inner cart typeC.Yokeonly really cares about destructors for its carts so it's fine to erase other information about the cart, as long as the backing data will still be destroyed at the same time.-
fn with_mut<'a, F>(self: &'a mut Self, f: F) where F: 'static + for<'b> FnOnce(&'b mut <Y as Yokeable<'a>>::Output)Mutate the stored
Yokeabledata.See [
Yokeable::transform_mut()] for why this operation is safe.Example
This can be used to partially mutate the stored data, provided no new borrowed data is introduced.
# use ; # use Rc; # use Cow; # use mem; # # # // also implements Yokeable // `load_object()` deserializes an object from a file let mut bar: = load_object; assert_eq!; assert!; assert_eq!; assert!; assert_eq!; bar.with_mut; assert_eq!; assert!; assert_eq!; // Unchanged and still Cow::Borrowed assert_eq!; assert!; # unsafefn wrap_cart_in_option(self: Self) -> Yoke<Y, Option<C>>Helper function allowing one to wrap the cart type
Cin anOption<T>.
impl<Y: for<'a> Yokeable<'a>, C: 'static + Sized + Send + Sync> Yoke<Y, Arc<C>>
fn erase_arc_cart(self: Self) -> Yoke<Y, ErasedArcCart>Allows type-erasing the cart in a
Yoke<Y, Arc<C>>.The yoke only carries around a cart type
Cfor its destructor, since it needs to be able to guarantee that its internal references are valid for the lifetime of the Yoke. As such, the actual type of the Cart is not very useful unless you wish to extract data out of it via [Yoke::backing_cart()]. Erasing the cart allows for one to mixYokes obtained from different sources.In case the cart type
Cis not already anArc<T>, you can use [Yoke::wrap_cart_in_arc()] to wrap it.✨ Enabled with the
allocCargo feature.Example
use Arc; use ErasedArcCart; use Yoke; let buffer1: = new; let buffer2: = Boxnew; let yoke1 = attach_to_cart; let yoke2 = attach_to_cart; let erased1: = yoke1.erase_arc_cart; // Wrap the Box in an Rc to make it compatible let erased2: = yoke2.wrap_cart_in_arc.erase_arc_cart; // Now erased1 and erased2 have the same type!
impl<Y: for<'a> Yokeable<'a>, C: 'static + Sized> Yoke<Y, Box<C>>
fn erase_box_cart(self: Self) -> Yoke<Y, ErasedBoxCart>Allows type-erasing the cart in a
Yoke<Y, Box<C>>.The yoke only carries around a cart type
Cfor its destructor, since it needs to be able to guarantee that its internal references are valid for the lifetime of the Yoke. As such, the actual type of the Cart is not very useful unless you wish to extract data out of it via [Yoke::backing_cart()]. Erasing the cart allows for one to mixYokes obtained from different sources.In case the cart type
Cis not alreadyBox<T>, you can use [Yoke::wrap_cart_in_box()] to wrap it.✨ Enabled with the
allocCargo feature.Example
use Rc; use ErasedBoxCart; use Yoke; let buffer1: = new; let buffer2: = Boxnew; let yoke1 = attach_to_cart; let yoke2 = attach_to_cart; // Wrap the Rc in an Box to make it compatible let erased1: = yoke1.wrap_cart_in_box.erase_box_cart; let erased2: = yoke2.erase_box_cart; // Now erased1 and erased2 have the same type!
impl<Y: for<'a> Yokeable<'a>, C: 'static + Sized> Yoke<Y, Rc<C>>
fn erase_rc_cart(self: Self) -> Yoke<Y, ErasedRcCart>Allows type-erasing the cart in a
Yoke<Y, Rc<C>>.The yoke only carries around a cart type
Cfor its destructor, since it needs to be able to guarantee that its internal references are valid for the lifetime of the Yoke. As such, the actual type of the Cart is not very useful unless you wish to extract data out of it via [Yoke::backing_cart()]. Erasing the cart allows for one to mixYokes obtained from different sources.In case the cart type
Cis not already anRc<T>, you can use [Yoke::wrap_cart_in_rc()] to wrap it.✨ Enabled with the
allocCargo feature.Example
use Rc; use ErasedRcCart; use Yoke; let buffer1: = new; let buffer2: = Boxnew; let yoke1 = attach_to_cart; let yoke2 = attach_to_cart; let erased1: = yoke1.erase_rc_cart; // Wrap the Box in an Rc to make it compatible let erased2: = yoke2.wrap_cart_in_rc.erase_rc_cart; // Now erased1 and erased2 have the same type!
impl<Y: for<'a> Yokeable<'a>, C: CartablePointerLike> Yoke<Y, Option<C>>
fn convert_cart_into_option_pointer(self: Self) -> Yoke<Y, CartableOptionPointer<C>>Converts a
Yoke<Y, Option<C>>toYoke<Y, CartableOptionPointer<C>>for better niche optimization when stored as a field.Examples
use Cow; use Yoke; let yoke: = attach_to_cart; let yoke_option = yoke.wrap_cart_in_option; let yoke_option_pointer = yoke_option.convert_cart_into_option_pointer;The niche improves stack sizes:
use Yoke; use CartableOptionPointer; use size_of; use Rc; // The data struct is 6 words: # # const W: usize = ; assert_eq!; // An enum containing the data struct with an `Option<Rc>` cart is 8 words: assert_eq!; // When using `CartableOptionPointer``, we need only 7 words for the same behavior: assert_eq!;
impl<Y: for<'a> Yokeable<'a>, C: CartablePointerLike> Yoke<Y, CartableOptionPointer<C>>
fn try_into_yokeable(self: Self) -> Result<Y, Self>Obtain the yokeable out of a
Yoke<Y, CartableOptionPointer<C>>if possible.If the cart is
None, this returnsOk, but if the cart isSome, this returnsselfas an error.
impl<Y: for<'a> Yokeable<'a>, C: StableDeref> Yoke<Y, C>
fn attach_to_cart<F>(cart: C, f: F) -> Self where F: for<'de> FnOnce(&'de <C as Deref>::Target) -> <Y as Yokeable<'de>>::Output, <C as Deref>::Target: 'staticConstruct a
Yokeby yokeing an object to a cart in a closure.The closure can read and write data outside of its scope, but data it returns may borrow only from the argument passed to the closure.
See also [
Yoke::try_attach_to_cart()] to return aResultfrom the closure.Call sites for this function may not compile pre-1.61; if this still happens, use [
Yoke::attach_to_cart_badly()] and file a bug.Examples
# use Yoke; # use Rc; # use Cow; # let yoke: = load_object; assert_eq!; assert!;Write the number of consumed bytes to a local variable:
# use Yoke; # use Rc; # use Cow; # let = load_object; assert_eq!; assert!; assert_eq!;fn try_attach_to_cart<E, F>(cart: C, f: F) -> Result<Self, E> where F: for<'de> FnOnce(&'de <C as Deref>::Target) -> Result<<Y as Yokeable<'de>>::Output, E>, <C as Deref>::Target: 'staticConstruct a
Yokeby yokeing an object to a cart. If an error occurs in the deserializer function, the error is passed up to the caller.Call sites for this function may not compile pre-1.61; if this still happens, use [
Yoke::try_attach_to_cart_badly()] and file a bug.fn attach_to_cart_badly(cart: C, f: for<'de> fn(_: &'de <C as Deref>::Target) -> <Y as Yokeable<'de>>::Output) -> SelfUse [
Yoke::attach_to_cart()].This was needed because the pre-1.61 compiler couldn't always handle the FnOnce trait bound.
fn try_attach_to_cart_badly<E>(cart: C, f: for<'de> fn(_: &'de <C as Deref>::Target) -> Result<<Y as Yokeable<'de>>::Output, E>) -> Result<Self, E>Use [
Yoke::try_attach_to_cart()].This was needed because the pre-1.61 compiler couldn't always handle the FnOnce trait bound.
impl<T> Any for Yoke<Y, C>
fn type_id(self: &Self) -> TypeId
impl<T> Borrow for Yoke<Y, C>
fn borrow(self: &Self) -> &T
impl<T> BorrowMut for Yoke<Y, C>
fn borrow_mut(self: &mut Self) -> &mut T
impl<T> CloneToUninit for Yoke<Y, C>
unsafe fn clone_to_uninit(self: &Self, dest: *mut u8)
impl<T> ErasedDestructor for Yoke<Y, C>
impl<T> From for Yoke<Y, C>
fn from(t: T) -> TReturns the argument unchanged.
impl<T> ToOwned for Yoke<Y, C>
fn to_owned(self: &Self) -> Tfn clone_into(self: &Self, target: &mut T)
impl<T, U> Into for Yoke<Y, C>
fn into(self: Self) -> UCalls
U::from(self).That is, this conversion is whatever the implementation of
[From]<T> for Uchooses to do.
impl<T, U> TryFrom for Yoke<Y, C>
fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>
impl<T, U> TryInto for Yoke<Y, C>
fn try_into(self: Self) -> Result<U, <U as TryFrom<T>>::Error>
impl<Y, C> Freeze for Yoke<Y, C>
impl<Y, C> RefUnwindSafe for Yoke<Y, C>
impl<Y, C> Send for Yoke<Y, C>
impl<Y, C> Sync for Yoke<Y, C>
impl<Y, C> Unpin for Yoke<Y, C>
impl<Y, C> UnsafeUnpin for Yoke<Y, C>
impl<Y, C> UnwindSafe for Yoke<Y, C>
impl<Y: for<'a> Yokeable<'a>, C: CloneableCart> Clone for Yoke<Y, C>
fn clone(self: &Self) -> Self
impl<Y: for<'a> Yokeable<'a>, C: core::fmt::Debug> Debug for Yoke<Y, C>
fn fmt(self: &Self, f: &mut Formatter<'_>) -> Result