Skip to content

Commit

Permalink
Add yoke crate
Browse files Browse the repository at this point in the history
  • Loading branch information
Manishearth committed May 5, 2021
1 parent 478c4a9 commit 93510a4
Show file tree
Hide file tree
Showing 7 changed files with 473 additions and 0 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ members = [
"utils/litemap",
"utils/pattern",
"utils/writeable",
"utils/yoke",
"utils/zerovec",
]

Expand Down
14 changes: 14 additions & 0 deletions utils/yoke/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "yoke"
version = "0.1.0"
authors = ["Manish Goregaokar <manishsmail@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
zerovec = { path = "../zerovec/", version = "0.2.0", optional = true}

[dev-dependencies]
bincode = "1.3.3"
serde = "1.0.125"
108 changes: 108 additions & 0 deletions utils/yoke/src/cart.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::{Yoke, Yokeable};
use std::rc::Rc;
use std::sync::Arc;

/// A [`Cart`] is a type that is acceptable as backing storage in a [`Yoke`].
///
/// The essential invariant that must be maintained by implementors is that `Self::Inner` references
/// obtained from this type via `Self::get_inner()` must continue to be valid for the
/// duration of this type provided that this type is never accessed via `&mut` references.
///
/// For example, `Vec<u8>` is a valid [`Cart`], however `Vec<RefCell<u8>>` is not, because
/// in the latter type it is possible to use interior mutation to change the data.
///
/// In general, this means "no interior mutability", though interior mutability can be fine
/// if the interior mutability is to something that does not affect references. For example,
/// `Rc<[u8]>` does have interior mutability in the refcount, but you cannot get references
/// to it, so it's fine. On the other hand, `Weak<Box<[u8]>>` cannot be a valid [`Cart`] because
/// it is possible for the backing buffer to be cleaned up without warning.
///
/// Common [`Cart`] types to use with [`Yoke`] are ones wrapping `[u8]` or `str`
/// (`Box<[u8]>`, `Rc<[u8]>`, etc) since those are typical inputs to zero-copy
/// deserialization and parsing.
///
/// Typically the [`Cart`] trait will be implemented by smart pointers whilst [`Cartable`] is used
/// as a helper to talk about types that have the same property provided they are not moved, sucn that
/// [`Cart`] can be implemented on wrappers that can maintain this property even if they are moved.
pub unsafe trait Cart {
type Inner: ?Sized;
/// Get the inner data
fn get_inner(&self) -> &Self::Inner;
}

/// [`Cartable`] is a helper trait for implementng [`Cart`]. It has a similar invariant:
/// all references obtained from this type must continue to be valid for the lifetime
/// of this type provided that this type is never moved or accessed via an `&mut` reference.
///
/// Essentially, this means "no interior mutability", however interior mutability which cannot be
/// seen from the outside is fine.
pub unsafe trait Cartable {}

unsafe impl<T: Cartable + ?Sized> Cart for Rc<T> {
type Inner = T;
fn get_inner(&self) -> &Self::Inner {
&**self
}
}

unsafe impl<T: Cartable + ?Sized> Cart for Arc<T> {
type Inner = T;
fn get_inner(&self) -> &Self::Inner {
&**self
}
}

unsafe impl<T: Cartable + ?Sized> Cart for Box<T> {
type Inner = T;
fn get_inner(&self) -> &Self::Inner {
&**self
}
}

unsafe impl<T: Cartable> Cart for Vec<T> {
type Inner = [T];
fn get_inner(&self) -> &Self::Inner {
&**self
}
}

unsafe impl Cart for String {
type Inner = str;
fn get_inner(&self) -> &Self::Inner {
&**self
}
}

unsafe impl<T: Cartable> Cartable for [T] {}
unsafe impl<T: Cart> Cartable for Option<T> {}
unsafe impl Cartable for str {}
unsafe impl Cartable for String {}
unsafe impl Cartable for bool {}
unsafe impl Cartable for char {}
unsafe impl Cartable for u8 {}
unsafe impl Cartable for u16 {}
unsafe impl Cartable for u32 {}
unsafe impl Cartable for u64 {}
unsafe impl Cartable for u128 {}
unsafe impl Cartable for i8 {}
unsafe impl Cartable for i16 {}
unsafe impl Cartable for i32 {}
unsafe impl Cartable for i64 {}
unsafe impl Cartable for i128 {}

/// This is a neat implementation; it allows one to build certain kinds of
/// caches by nesting [`Yoke`]s where both the [`Cart`] and the parsed [`Yokeable`]
/// are cached.
///
/// Essentially, this allows the construction of the type
/// `Yoke<Rc<Yoke<C, Y>>, Y>` and `Weak<Yoke<C, Y>>`: the `Weak` can be stored
/// in your cache, whereas the `Yoke<Rc<..>, Y>` is passed around. The cache entry
/// will automatically clean (most of) itself up when all `Rc`s go out of scope.
///
/// The resultant [`Yoke`] type is a bit more complicated but it's not less efficient
/// since all [`Yoke`] operations except the destructor ignore the [`Cart`].
unsafe impl<Y: for<'a> Yokeable<'a>, C: Cart> Cartable for Yoke<Y, C> {}
11 changes: 11 additions & 0 deletions utils/yoke/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

mod cart;
mod yoke;
mod yokeable;

pub use crate::cart::{Cart, Cartable};
pub use crate::yoke::Yoke;
pub use crate::yokeable::Yokeable;
151 changes: 151 additions & 0 deletions utils/yoke/src/yoke.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use crate::Cart;
use crate::Cartable;
use crate::Yokeable;
use std::rc::Rc;
use std::sync::Arc;

/// 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.
///
/// `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 erase
/// the lifetime and make it dynamic.
///
/// `C` is the "cart", which `Y` may contain references to. A [`Yoke`] can be constructed
/// with such references using [`Yoke::attach_to_cart()`].
///
/// # Example
///
/// For example, we can use this to store zero-copy deserialized data in a cache:
///
/// ```rust
/// # use yoke::{Yoke, Yokeable};
/// # use std::rc::Rc;
/// # use std::borrow::Cow;
/// # fn load_from_cache(_filename: &str) -> Rc<[u8]> {
/// # // dummy implementation
/// # Rc::new([0x68, 0x65, 0x6c, 0x6c, 0x6f])
/// # }
///
/// fn load_object(filename: &str) -> Yoke<Cow<'static, [u8]>, Rc<[u8]>> {
/// let rc: Rc<[u8]> = load_from_cache(filename);
/// Yoke::<Cow<'static, [u8]>, Rc<[u8]>>::attach_to_cart_but_worse(rc, |data: &[u8]| {
/// bincode::deserialize_from(data).unwrap()
/// })
/// }
/// ```
///
pub struct Yoke<Y: for<'a> Yokeable<'a>, C: Cart> {
// must be the first field for drop order
// this will have a 'static lifetime parameter, that parameter is a lie
yokeable: Y,
cart: C,
}

impl<Y: for<'a> Yokeable<'a>, C: Cart> Yoke<Y, C> {
/// Construct a new [`Yoke`] from static data. There will be no
/// references to `cart` here, this is good for e.g. constructing fully owned
/// [`Yoke`]s with no internal borrowing.
pub fn new(cart: C, yokeable: Y) -> Self {
Self { yokeable, cart }
}

/// Obtain 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>`
pub fn get<'a>(&'a self) -> &'a <Y as Yokeable<'a>>::Output {
self.yokeable.transform()
}

/// Get a reference to the backing cart.
pub fn backing_cart(&self) -> &C {
&self.cart
}

pub fn with_mut<'a, F>(&'a mut self, f: F)
where
F: 'static + for<'b> FnOnce(&'b mut <Y as Yokeable<'a>>::Output),
{
self.yokeable.with_mut(f)
}

pub fn attach_to_cart<F>(cart: C, f: F) -> Self
where
F: for<'de> FnOnce(&'de C::Inner) -> <Y as Yokeable<'de>>::Output,
{
let deserialized = f(cart.get_inner());
Self {
yokeable: unsafe { Y::make(deserialized) },
cart,
}
}

/// Temporary version of attach_to_cart that doesn't hit https://github.com/rust-lang/rust/issues/84937
pub fn attach_to_cart_but_worse(
cart: C,
f: for<'de> fn(&'de C::Inner) -> <Y as Yokeable<'de>>::Output,
) -> Self {
let deserialized = f(cart.get_inner());
Self {
yokeable: unsafe { Y::make(deserialized) },
cart,
}
}
}

// clone impls only work for reference counted objects, otherwise you should be
// cloning `backing_cart()` and reusing `attach_to_cart()`
impl<Y: for<'a> Yokeable<'a>, T: Cartable + ?Sized> Clone for Yoke<Y, Rc<T>>
where
for<'a> <Y as Yokeable<'a>>::Output: Clone,
{
fn clone(&self) -> Self {
Yoke {
yokeable: unsafe { Y::make(self.get().clone()) },
cart: self.cart.clone(),
}
}
}

impl<Y: for<'a> Yokeable<'a>, T: Cartable + ?Sized> Clone for Yoke<Y, Arc<T>>
where
for<'a> <Y as Yokeable<'a>>::Output: Clone,
{
fn clone(&self) -> Self {
Yoke {
yokeable: unsafe { Y::make(self.get().clone()) },
cart: self.cart.clone(),
}
}
}

// #[test]
// // See https://github.com/rust-lang/rust/issues/84937
// fn this_test_is_broken() {
// use crate::{Yoke, Yokeable};
// use std::borrow::Cow;
// use std::rc::Rc;
// fn load_from_cache(_filename: &str) -> Rc<[u8]> {
// // dummy implementation
// Rc::new([0x68, 0x65, 0x6c, 0x6c, 0x6f])
// }

// fn load_object(filename: &str) -> Yoke<Cow<'static, [u8]>, Rc<[u8]>> {
// let rc: Rc<[u8]> = load_from_cache(filename);
// Yoke::<Cow<'static, [u8]>, Rc<[u8]>>::attach_to_cart(rc, deserialize);
// unimplemented!()
// }
// fn deserialize<'d>(data: &'d [u8]) -> <Cow<'static, [u8]> as Yokeable<'d>>::Output {
// bincode::deserialize_from(data).unwrap()
// }
// }
Loading

0 comments on commit 93510a4

Please sign in to comment.