Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compile-time tracking of "value owner" with lifetimes

Tags:

rust

lifetime

Is there any way to track value "owner" so that it passing it to other owners is a compile-time error? I've been looking into invariant lifetimes and the generativity crate, but my rust-fu is not quite up to the task.

Code probably explains what I'm trying to do the best:

use core::marker::PhantomData;

// The lifetime `'a` is somehow only valid for the `Bar` that created `Foo`
#[derive(Default, Copy, Clone)]
struct Foo<'a> {
    phantom: PhantomData<&'a Owner>,
}

#[derive(Default)]
struct Owner {}

impl Owner {
    fn get<'a, 'b: 'a>(&'a self) -> Foo<'b> {
        Foo::<'b>::default();
    }
    
    // Somehow requires that the unique lifetime of `self` applies also to `Foo`.
    fn set<'a>(&'a mut self, _foo: Foo<'a>) {
        // whatever code
    }
}

fn main() {
    let mut owner = Owner::default();
    let mut owner2 = Owner::default();
    
    let anon_foo = Foo::default();
    let foo2 = owner2.get();
    
    owner.set(foo2); // should not compile
    owner.set(anon_foo); // also should not compile
    
    owner2.set(foo2); // compiles!
}

This is a rather theoretical question, I'm not trying to solve a specific problem, just trying to see if such zero-cost abstraction construct is possible in Rust.

EDIT: This is not required to work in non-trivial cases, it just has to fail-safe (should never allow set if compiler cannot prove the Owner is the actual owner). Also, the creation of the Owner struct can be with a macro, it doesn't have to be runtime constructable.

like image 839
Kryštof Vosyka Avatar asked Oct 16 '25 07:10

Kryštof Vosyka


1 Answers

I think that instead of lifetimes, that have variance and weird rules, it would be easier to use a token type. You can create a local nested token type by using a macro, or requiring that the users specify the token manually.

This solution tracks the owner by its creation point, so if you create two owners in the same line of code, they will share ownership. Consider that a non-trivial case.

The code would be something like this:

struct Foo<T> {
    _pd: std::marker::PhantomData<T>,
}
//Default is only for anonymous Foo
impl Default for Foo<()> {
    fn default() -> Self {
        Foo {
            _pd: std::marker::PhantomData,
        }
    }
}

struct Owner<T> {
    _pd: std::marker::PhantomData<T>,
}
impl<T> Default for Owner<T> {
    fn default() -> Self {
        Owner {
            _pd: std::marker::PhantomData,
        }
    }
}

impl<T> Owner<T> {
    fn get(&self) -> Foo<T> {
        Foo {
            _pd: std::marker::PhantomData,
        }
    }
    fn set(&self, foo: Foo<T>) {
    }
}

macro_rules! new_owner {
    () => {{
        struct Tok;
        Owner::<Tok>::default()
    }}
}

This code passes your test case perfectly fine:

fn main() {
    let mut vec = Vec::new();
    for i in 0..2 {
        vec.push(new_owner!());
    }
    let mut owner = new_owner!();
    let mut owner2 = new_owner!();
    let anon_foo = Foo::default();
    
    let foo2 = owner2.get();
    
    owner.set(foo2); // error
    owner.set(anon_foo); // error
    
    owner2.set(foo2); // ok!
}

The error emitted is something like:

error[E0308]: mismatched types
  --> src/main.rs:53:15
   |
53 |     owner.set(foo2); // error
   |           --- ^^^^ expected `main::Tok`, found a different `main::Tok`
   |           |
   |           arguments to this method are incorrect
   |
   = note: expected struct `Foo<main::Tok>` (`main::Tok`)
              found struct `Foo<main::Tok>` (`main::Tok`)

Since the token is build by the macro, if you somehow create both owners in the same macro call, it will compile:

fn main() {
    let mut owners = Vec::new();
    for _ in 0..2 {
        owners.push(new_owner!()); // just one macro invocation
    }
    let foo = owners[0].get();
    owners[1].set(foo); // OK, same token type.
}
like image 179
rodrigo Avatar answered Oct 18 '25 22:10

rodrigo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!