Skip to content

Commit 361d975

Browse files
committed
Add yoke crate
1 parent 478c4a9 commit 361d975

File tree

6 files changed

+189
-0
lines changed

6 files changed

+189
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ members = [
2929
"utils/litemap",
3030
"utils/pattern",
3131
"utils/writeable",
32+
"utils/yoke",
3233
"utils/zerovec",
3334
]
3435

utils/yoke/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "yoke"
3+
version = "0.1.0"
4+
authors = ["Manish Goregaokar <[email protected]>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
zerovec = { path = "../zerovec/", version = "0.2.0", optional = true}

utils/yoke/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// This file is part of ICU4X. For terms of use, please see the file
2+
// called LICENSE at the top level of the ICU4X source tree
3+
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4+
5+
mod cart;
6+
mod yoke;
7+
mod yokeable;
8+
9+
pub use cart::{Cart, Cartable};
10+
pub use yoke::Yoke;
11+
pub use yokeable::Yokeable;

utils/yoke/src/yoke.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// This file is part of ICU4X. For terms of use, please see the file
2+
// called LICENSE at the top level of the ICU4X source tree
3+
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4+
5+
use crate::Cart;
6+
use crate::Cartable;
7+
use crate::Yokeable;
8+
use std::rc::Rc;
9+
use std::sync::Arc;
10+
11+
/// A Cow-like borrowed object "yoked" to its backing data.
12+
///
13+
/// This allows things like zero copy deserialized data to carry around
14+
/// shared references to their backing buffer.
15+
///
16+
/// `Y` (the [`Yokeable`]) is the object containing the references,
17+
/// and will typically be of the form `Foo<'static>`. The `'static` is
18+
/// not the actual lifetime of the data, rather it is a convenient way to erase
19+
/// the lifetime and make it dynamic.
20+
///
21+
/// `C` is the "cart", which `Y` may contain references to. A [`Yoke`] can be constructed
22+
/// with such references using [`Yoke::attach_to_cart()`].
23+
pub struct Yoke<Y: for<'a> Yokeable<'a>, C: Cart> {
24+
// must be the first field for drop order
25+
// this will have a 'static lifetime parameter, that parameter is a lie
26+
yokeable: Y,
27+
cart: C,
28+
}
29+
30+
impl<Y: for<'a> Yokeable<'a>, C: Cart> Yoke<Y, C> {
31+
/// Construct a new [`Yoke`] from static data. There will be no
32+
/// references to `cart` here, this is good for e.g. constructing fully owned
33+
/// [`Yoke`]s with no internal borrowing.
34+
pub fn new(cart: C, yokeable: Y) -> Self {
35+
Self { yokeable, cart }
36+
}
37+
38+
/// Obtain a valid reference to the yokeable data
39+
///
40+
/// This essentially transforms the lifetime of the internal yokeable data to
41+
/// be valid.
42+
/// For example, if you're working with a `Yoke<Cow<'static, T>, C>`, this
43+
/// will return an `&'a Cow<'a, T>`
44+
pub fn get<'a>(&'a self) -> &'a <Y as Yokeable<'a>>::Output {
45+
self.yokeable.transform()
46+
}
47+
48+
/// Get a reference to the backing cart.
49+
pub fn backing_cart(&self) -> &C {
50+
&self.cart
51+
}
52+
53+
pub fn with_mut<'a, F>(&'a mut self, f: F)
54+
where
55+
F: 'static + FnOnce(&'a mut <Y as Yokeable<'a>>::Output),
56+
{
57+
self.yokeable.with_mut(f)
58+
}
59+
60+
pub fn attach_to_cart<F>(cart: C, f: F) -> Self
61+
where
62+
for<'de> F: FnOnce(&'de C::Inner) -> <Y as Yokeable<'de>>::Output,
63+
{
64+
let deserialized = f(cart.get_inner());
65+
Self {
66+
yokeable: unsafe { Y::make(deserialized) },
67+
cart,
68+
}
69+
}
70+
}
71+
72+
// clone impls only work for reference counted objects, otherwise you should be
73+
// cloning `backing_cart()` and reusing `attach_to_cart()`
74+
impl<Y: for<'a> Yokeable<'a>, T: Cartable + ?Sized> Clone for Yoke<Y, Rc<T>>
75+
where
76+
for<'a> <Y as Yokeable<'a>>::Output: Clone,
77+
{
78+
fn clone(&self) -> Self {
79+
Yoke {
80+
yokeable: unsafe { Y::make(self.get().clone()) },
81+
cart: self.cart.clone(),
82+
}
83+
}
84+
}
85+
86+
impl<Y: for<'a> Yokeable<'a>, T: Cartable + ?Sized> Clone for Yoke<Y, Arc<T>>
87+
where
88+
for<'a> <Y as Yokeable<'a>>::Output: Clone,
89+
{
90+
fn clone(&self) -> Self {
91+
Yoke {
92+
yokeable: unsafe { Y::make(self.get().clone()) },
93+
cart: self.cart.clone(),
94+
}
95+
}
96+
}

utils/yoke/src/yokeable.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// This file is part of ICU4X. For terms of use, please see the file
2+
// called LICENSE at the top level of the ICU4X source tree
3+
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4+
5+
use std::borrow::{Cow, ToOwned};
6+
use std::mem;
7+
8+
pub unsafe trait Yokeable<'a>: 'static {
9+
type Output: 'a + Sized; // MUST be `Self` with the lifetime swapped
10+
// used by `SharedData::get()`
11+
fn transform(&'a self) -> &'a Self::Output;
12+
13+
// Used for zero-copy deserialization. Safety constraint: `Self` must be
14+
// destroyed before the data `Self::Output` was deserialized
15+
// from is
16+
unsafe fn make(from: Self::Output) -> Self;
17+
18+
fn with_mut<F>(&'a mut self, f: F)
19+
where
20+
F: 'static + FnOnce(&'a mut Self::Output);
21+
}
22+
23+
unsafe impl<'a, T: 'static + ToOwned + ?Sized> Yokeable<'a> for Cow<'static, T>
24+
where
25+
<T as ToOwned>::Owned: Sized,
26+
{
27+
type Output = Cow<'a, T>;
28+
fn transform(&'a self) -> &'a Cow<'a, T> {
29+
self
30+
}
31+
32+
unsafe fn make(from: Cow<'a, T>) -> Self {
33+
debug_assert!(mem::size_of::<Cow<'a, T>>() == mem::size_of::<Self>());
34+
// i hate this
35+
// unfortunately Rust doesn't think `mem::transmute` is possible since it's not sure the sizes
36+
// are the same
37+
let ret = mem::transmute_copy(&from);
38+
mem::forget(from);
39+
ret
40+
}
41+
42+
fn with_mut<F>(&'a mut self, f: F)
43+
where
44+
F: 'static + FnOnce(&'a mut Self::Output),
45+
{
46+
unsafe { f(mem::transmute(self)) }
47+
}
48+
}
49+
50+
struct Foo {
51+
str: String,
52+
cow: Cow<'static, str>,
53+
}
54+
55+
// The following code should NOT compile!!!
56+
//
57+
// fn unsound<'a>(foo: &'a mut Foo) {
58+
// let a: &str = &foo.str;
59+
// foo.cow.with_mut(|cow| *cow = Cow::Borrowed(a));
60+
// }
61+
62+
fn sound<'a>(foo: &'a mut Foo) {
63+
foo.cow.with_mut(move |cow| cow.to_mut().push('a'));
64+
}

0 commit comments

Comments
 (0)