Is this alternative to GhostCell sound?

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

Quick UB here.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=581f73359650e237b3f3168986f8f4c6

2 Likes

Damn, I can't believe I didn't see that. I don't see how that's fixable, so looks like this is unsound.

Simlar example. Your type is unique for each call site, not for each invocation. There was some related discussion in this IRLO thread recently.

3 Likes

Yeah, that's a shame. I'd love to see better support for unique types/brands in rust in the future.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.