After the GhostCell paper showed up on reddit a few days ago, I've been thinking about whether there is a way around having to use invariant lifetimes inside a closure.
So, I came up with a version of GhostCell
that uses a unique existential type as a token instead of an invariant lifetime. I'm not 100% sure if it's sound, so I'd appreciate a review of it.
Here are the interesting bits:
pub unsafe trait Token: Send + Sync {}
macro_rules! create_token {
() => {{
use $crate::Token;
fn mint_token() -> impl Token {
struct ThrowawayType;
unsafe impl Token for ThrowawayType {}
ThrowawayType
}
mint_token()
}};
}
pub struct TokenCell<T, Tok> {
_marker: PhantomData<Tok>,
value: UnsafeCell<T>,
}
impl<T, Tok> TokenCell<T, Tok> {
pub const fn new(value: T) -> Self {
TokenCell {
_marker: PhantomData,
value: UnsafeCell::new(value),
}
}
}
impl<T, Tok: Token> TokenCell<T, Tok> {
pub fn borrow<'a>(&'a self, _token: &'a Tok) -> &'a T {
unsafe {
&*self.value.get()
}
}
pub fn borrow_mut<'a>(&'a self, _token: &'a mut Tok) -> &'a mut T {
unsafe {
&mut *self.value.get()
}
}
}
It's used like this:
let mut token = create_token!();
let data = TokenCell::new(vec![1, 2, 3]);
data.borrow_mut(&mut token).push(4);
println!("{:?}", data.borrow(&token));
compared to GhostCell
, which is used like this:
GhostToken::new(|token| {
let data = GhostCell::new(vec![1, 2, 3]);
data.borrow_mut(&mut token).push(4);
println!("{:?}", data.borrow(&token));
});
Additionally, it's easy to pass a TokenCell
up the stack, whereas a GhostCell
must stay within the GhostToken::new(...)
closure.
Here's the playground link to the TokenCell
implementation: Rust Playground