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 3, 2021
1 parent 478c4a9 commit 361d975
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 0 deletions.
7 changes: 7 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
10 changes: 10 additions & 0 deletions utils/yoke/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[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}
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 cart::{Cart, Cartable};
pub use yoke::Yoke;
pub use yokeable::Yokeable;
96 changes: 96 additions & 0 deletions utils/yoke/src/yoke.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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()`].
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 + FnOnce(&'a mut <Y as Yokeable<'a>>::Output),
{
self.yokeable.with_mut(f)
}

pub fn attach_to_cart<F>(cart: C, f: F) -> Self
where
for<'de> F: FnOnce(&'de C::Inner) -> <Y as Yokeable<'de>>::Output,
{
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(),
}
}
}
64 changes: 64 additions & 0 deletions utils/yoke/src/yokeable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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 std::borrow::{Cow, ToOwned};
use std::mem;

pub unsafe trait Yokeable<'a>: 'static {
type Output: 'a + Sized; // MUST be `Self` with the lifetime swapped
// used by `SharedData::get()`
fn transform(&'a self) -> &'a Self::Output;

// Used for zero-copy deserialization. Safety constraint: `Self` must be
// destroyed before the data `Self::Output` was deserialized
// from is
unsafe fn make(from: Self::Output) -> Self;

fn with_mut<F>(&'a mut self, f: F)
where
F: 'static + FnOnce(&'a mut Self::Output);
}

unsafe impl<'a, T: 'static + ToOwned + ?Sized> Yokeable<'a> for Cow<'static, T>
where
<T as ToOwned>::Owned: Sized,
{
type Output = Cow<'a, T>;
fn transform(&'a self) -> &'a Cow<'a, T> {
self
}

unsafe fn make(from: Cow<'a, T>) -> Self {
debug_assert!(mem::size_of::<Cow<'a, T>>() == mem::size_of::<Self>());
// i hate this
// unfortunately Rust doesn't think `mem::transmute` is possible since it's not sure the sizes
// are the same
let ret = mem::transmute_copy(&from);
mem::forget(from);
ret
}

fn with_mut<F>(&'a mut self, f: F)
where
F: 'static + FnOnce(&'a mut Self::Output),
{
unsafe { f(mem::transmute(self)) }
}
}

struct Foo {
str: String,
cow: Cow<'static, str>,
}

// The following code should NOT compile!!!
//
// fn unsound<'a>(foo: &'a mut Foo) {
// let a: &str = &foo.str;
// foo.cow.with_mut(|cow| *cow = Cow::Borrowed(a));
// }

fn sound<'a>(foo: &'a mut Foo) {
foo.cow.with_mut(move |cow| cow.to_mut().push('a'));
}

0 comments on commit 361d975

Please sign in to comment.