We often have the following designs:
struct Context {}
struct Resource {}
impl Context {
pub fn new() -> Self {
Self {}
}
pub fn create_resource(&self) -> Resource {
todo!()
}
pub fn delete_resource(&self, res: Resource) {
// Check the context of the resource
todo!()
}
}
However, our users may create multiple 'Context', so we have to check owner of Resource
at runtime.
Therefore, I try to avoid it by following the design:
pub type Invariant<'s> = PhantomData<*mut &'s ()>;
pub struct Symbol<'s>(Invariant<'s>);
pub fn for_symbol0<F: FnOnce() -> R, R>(f: F) -> R {
f()
}
pub fn for_symbol1<F: for<'s1> FnOnce(Symbol<'s1>) -> R, R>(f: F) -> R {
f(Symbol(PhantomData))
}
pub fn for_symbol2<F: for<'s1, 's2> FnOnce(Symbol<'s1>, Symbol<'s2>) -> R, R>(f: F) -> R {
f(Symbol(PhantomData), Symbol(PhantomData))
}
Only a few changes by the designer are required:
struct NewContext<'c> {
lifetime: Symbol<'c>,
}
struct NewResource<'c> {
p: Invariant<'c>,
}
impl<'c> NewContext<'c> {
pub fn new(lifetime: Symbol<'c>) -> Self {
Self {
lifetime
}
}
pub fn create_resource(&self) -> NewResource<'c> {
todo!()
}
pub fn delete_resource(&self, res: NewResource<'c>) {
todo!()
}
}
fn main() {
for_symbol2(|l1, l2| {
let c1 = NewContext::new(l1);
let c2 = NewContext::new(l2);
let res = c1.create_resource();
// compile success
c1.delete_resource(res);
// compile fail
// c2.delete_resource(res);
});
}