diff --git a/Cargo.lock b/Cargo.lock index 2a00eef0141..2b242a473d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2441,6 +2441,13 @@ dependencies = [ "icu_benchmark_macros", ] +[[package]] +name = "yoke" +version = "0.1.0" +dependencies = [ + "zerovec", +] + [[package]] name = "zerovec" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 539f4985679..50bad3f890d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "utils/litemap", "utils/pattern", "utils/writeable", + "utils/yoke", "utils/zerovec", ] diff --git a/utils/yoke/Cargo.toml b/utils/yoke/Cargo.toml new file mode 100644 index 00000000000..fdc0cf03386 --- /dev/null +++ b/utils/yoke/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "yoke" +version = "0.1.0" +authors = ["Manish Goregaokar "] +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} diff --git a/utils/yoke/src/lib.rs b/utils/yoke/src/lib.rs new file mode 100644 index 00000000000..901e16bb48b --- /dev/null +++ b/utils/yoke/src/lib.rs @@ -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; diff --git a/utils/yoke/src/yoke.rs b/utils/yoke/src/yoke.rs new file mode 100644 index 00000000000..b73000f711b --- /dev/null +++ b/utils/yoke/src/yoke.rs @@ -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 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 Yokeable<'a>, C: Cart> Yoke { + /// 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, C>`, this + /// will return an `&'a Cow<'a, T>` + pub fn get<'a>(&'a self) -> &'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 >::Output), + { + self.yokeable.with_mut(f) + } + + pub fn attach_to_cart(cart: C, f: F) -> Self + where + for<'de> F: FnOnce(&'de C::Inner) -> >::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 Yokeable<'a>, T: Cartable + ?Sized> Clone for Yoke> +where + for<'a> >::Output: Clone, +{ + fn clone(&self) -> Self { + Yoke { + yokeable: unsafe { Y::make(self.get().clone()) }, + cart: self.cart.clone(), + } + } +} + +impl Yokeable<'a>, T: Cartable + ?Sized> Clone for Yoke> +where + for<'a> >::Output: Clone, +{ + fn clone(&self) -> Self { + Yoke { + yokeable: unsafe { Y::make(self.get().clone()) }, + cart: self.cart.clone(), + } + } +} diff --git a/utils/yoke/src/yokeable.rs b/utils/yoke/src/yokeable.rs new file mode 100644 index 00000000000..f81a7c78e70 --- /dev/null +++ b/utils/yoke/src/yokeable.rs @@ -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(&'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 + ::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::>() == mem::size_of::()); + // 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(&'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')); +}