Lifetime subtyping in trait impl

I am trying to create a system where objects are owned by an Arena and you can hold handles to them. This lets me create non-borrow checker friendly structures that I need (like graphs). This makes sure that when an Arena goes out of scope there are no references to it's data and it is safe to drop. I have a trait IntoObject that allocates and object and returns a handle.

What I want is to be able to define IntoObject on a Handle and it be essentially a no-op. But I want the compiler to statically verify that the new handle cannot outlive the old handle. I was trying to use Lifetime Subtyping to enforce this, but I am getting compiler messages saying that it can't prove that the lifetimes work. I need help deciphering the error and doing this the correct way.

Playground link

use std::marker::PhantomData;

pub enum Object {
    Int(i64),
    Float(f64),
}

#[derive(Copy, Clone)]
pub struct Handle<'a> {
    ptr: *const Object,
    marker: PhantomData<&'a ()>,
}

pub struct Arena {
    // not shown
}

impl Arena {
    fn alloc(&self, obj: Object) -> Handle {
        // real implentation not shown
        Handle {
            ptr: Box::into_raw(Box::new(obj)),
            marker: PhantomData,
        }
    }
}

pub trait IntoObject {
    fn into_object(self, arena: &Arena) -> Handle;
}

impl IntoObject for i64 {
    fn into_object(self, arena: &Arena) -> Handle {
        arena.alloc(Object::Int(self))
    }
}

impl IntoObject for f64 {
    fn into_object(self, arena: &Arena) -> Handle {
        arena.alloc(Object::Float(self))
    }
}

// This should be a no-op
impl<'a, 'b: 'a> IntoObject for Handle<'a> {
    fn into_object(self, arena: &'b Arena) -> Handle<'b> {
        self
    }
}

fn main() {

    let long_arena = Arena{};
    let long_handle = 10.into_object(&long_arena);
    {  
        let short_arena = Arena{};
        let short_handle = 3.3.into_object(&short_arena);

        // This should work
        let from_handle = long_handle.into_object(&short_arena);
    }


}

The problem is that Handle, unlike the other types, requires an arena of a specific lifetime - it doesn't work with any lifetime like the IntoObject trait requires. What you can do is add a lifetime parameter to the trait:

pub trait IntoObject<'arena> {
    fn into_object(self, arena: &'arena Arena) -> Handle<'arena>;
}

Types like i64 and f64 will implement this for any lifetime, whereas Handle<'a> will only implement it for one specific lifetime - 'a.

Edit: Playground link demonstrating this solution

3 Likes