-
Notifications
You must be signed in to change notification settings - Fork 180
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
478c4a9
commit 93510a4
Showing
7 changed files
with
473 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ members = [ | |
"utils/litemap", | ||
"utils/pattern", | ||
"utils/writeable", | ||
"utils/yoke", | ||
"utils/zerovec", | ||
] | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
// } | ||
// } |
Oops, something went wrong.