How to work around concrete lifetime issues without GATs?

I’m currently working on restor, and have hit abit of a roadblock.
You can clone the git repo and follow along, or use the examples provided here. Note, that if you’re using the git repo, be ready to be jumping around files using a ctrl+click (If you’re using IntelliJ)
I have, essentially the following setup:

use std::any::Any;
use std::ops::Deref;

trait Foo<'a> {
    type Borrowed: Deref<Target=dyn Any> + 'a;
}

impl<'a> Foo<'a> for usize {
    type Borrowed = &'a dyn Any;
}

struct Container<O>(Option<Box<dyn for<'a> Foo<'a, Borrowed=O>>>);

impl<'a> Container<&'a dyn Any> {
    pub fn new() -> Self {
        Container(None)
    }
    
    pub fn insert(&mut self, val: usize) {
        self.0 = Some(Box::new(val));
    }
}

fn main() {
    let mut x: Container<&dyn Any> = Container::new();
    x.insert(0);
}

Which, fails to compile, due to this error:

error[E0271]: type mismatch resolving `for<'a> <usize as Foo<'a>>::Borrowed == &dyn std::any::Any`
  --> src/main.rs:20:23
   |
20 |         self.0 = Some(Box::new(val));
   |                       ^^^^^^^^^^^^^ expected bound lifetime parameter 'a, found concrete lifetime
   |
   = note: required for the cast to the object type `dyn for<'a> Foo<'a, Borrowed=&dyn std::any::Any>`

I need to have the lifetime attached to the trait to be able to write the functions it contains, to assure that the return values of the functions live for a shorter lifetime than self.

Actual code
pub trait Unit<'a> {
    type Borrowed: Deref<Target = dyn Any> + 'a;
    type MutBorrowed: Deref<Target = dyn Any> + DerefMut + 'a;
    type Owned: Deref<Target = dyn Any> + DerefMut;

    fn one(&'a self) -> DynamicResult<Self::Borrowed>;
    fn one_mut(&'a self) -> DynamicResult<Self::MutBorrowed>;

    fn ind(&'a self, ind: usize) -> DynamicResult<Self::Borrowed>;
    fn ind_mut(&'a self, ind: usize) -> DynamicResult<Self::MutBorrowed>;

    fn extract(&self) -> DynamicResult<Self::Owned>;
    fn extract_ind(&self, ind: usize) -> DynamicResult<Self::Owned>;
    fn extract_many(&self) -> DynamicResult<Self::Owned>;

    fn insert_any(&self, new: Self::Owned) -> Option<(Self::Owned, ErrorDesc)>;

    fn id(&self) -> TypeId;
}

I don’t think that there’s an appropriate lifetime that I can attach to my boxed trait, so I use a HRTB,

Box<dyn for<'a> Foo<'a, Borrowed=O>>
Actual code
pub struct BlackBox<
    R: Deref<Target = dyn Any>,
    W: Deref<Target = dyn Any> + DerefMut,
    O: Deref<Target = dyn Any> + DerefMut,
> {
    data: HashMap<TypeId, Box<dyn for<'a> Unit<'a, Borrowed = R, MutBorrowed = W, Owned = O>>>,
}

But, to create/insert a HRTB trait object, I run into the above error, which I believe is due to the following:
for<'a> means any lifetime, which 'self (Or whatever you want to annotate it as) is not equal to or greater than 'a, because 'a could even be 'static, which my object isn’t.

Actual code
#[inline]
pub fn allocate_for<T: 'static + Send + Sync>(&mut self) {
    if !self.data.contains_key(&TypeId::of::<T>()) {
        self.data.insert(
            TypeId::of::<T>(),
            Box::new(RwLockUnit::new(StorageUnit::<T>::new())),
        );
    }
}

I’m not quite sure how I could get around this, because with GATs, my unit trait could become the following:

trait Foo {
    type Borrowed<'a>: Deref<Target=dyn Any> + 'a;
}
Potentially Actual Code
pub trait Unit {
    type Borrowed<'a>: Deref<Target = dyn Any> + 'a;
    type MutBorrowed<'a>: Deref<Target = dyn Any> + DerefMut + 'a;
    type Owned<'a>: Deref<Target = dyn Any> + DerefMut;

    fn one<'a>(&'a self) -> DynamicResult<Self::Borrowed<'a>>;
    fn one_mut<'a>(&'a self) -> DynamicResult<Self::MutBorrowed<'a>>;

    fn ind<'a>(&'a self, ind: usize) -> DynamicResult<Self::Borrowed<'a>>;
    fn ind_mut<'a>(&'a self, ind: usize) -> DynamicResult<Self::MutBorrowed<'a>>;

    fn extract(&self) -> DynamicResult<Self::Owned>;
    fn extract_ind(&self, ind: usize) -> DynamicResult<Self::Owned>;
    fn extract_many(&self) -> DynamicResult<Self::Owned>;

    fn insert_any(&self, new: Self::Owned) -> Option<(Self::Owned, ErrorDesc)>;

    fn id(&self) -> TypeId;
}

And no public lifetimes are exposed, eliminating lifetimes entirely. But, GATs are quite a ways away, so this isn’t possible. Does anyone have an idea as to what can be done to get around this?

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

The gist of it is that I made Container more generic, this allowed me to use the same lifetime 'a in the for<..> binder, this meant that all of the lifetimes aligned, and made this work!
This method will require lots of type aliases (otherwise there is just too much typing)

1 Like

Okay, that worked beautifully. My git repo has been updated. Now, I’m facing a bit of a dilemma. My definition for BlackBox is this:

pub struct BlackBox<U: ?Sized> {
    data: HashMap<TypeId, Box<U>>,
}

Essentially what you changed. My Unit trait has not changed

Unit trait
pub trait Unit<'a> {
    type Borrowed: Deref<Target = dyn Any> + 'a;
    type MutBorrowed: Deref<Target = dyn Any> + DerefMut + 'a;
    type Owned: Deref<Target = dyn Any> + DerefMut;
    // Some methods using these associated types
}

But now my impl for BlackBox has become the following:

impl<R: Deref<Target = dyn Any>, W: Deref<Target = dyn Any> + DerefMut>
    BlackBox<dyn for<'a> Unit<'a, Borrowed = R, MutBorrowed = W, Owned = Box<dyn Any>>>
{
    pub fn new() -> Self {
        Self {
            data: HashMap::new(),
        }
    }
}

Which compiles, but now I can’t run new on a type alias like so:

pub type DynamicStorage = BlackBox<
    dyn for<'a> Unit<
        'a,
        Borrowed = Ref<'a, dyn Any>,
        MutBorrowed = RefMut<'a, dyn Any>,
        Owned = Box<dyn Any>,
    >,
>;

let mut x = DynamicStorage::new();

Any idea why? I believe that the type alias should work for the generic impl. Unless I should be taking an approach more like the following:

impl<R: Deref<Target = dyn Any>, W: Deref<Target = dyn Any> + DerefMut, U: for<'a> Unit<'a, Borrowed=R, MutBorrowed=W, Owned=Box<dyn Any>> + ?Sized>
    BlackBox<U>
{
    pub fn new() -> Self {
        Self {
            data: HashMap::new(),
        }
    }
}

But that doesn’t work either, the library compiles but the api is unusable. Any ideas?

You can’t specify associated types in the generic impl, basically what you are saying when you do is: the associated types don’t depend on the lifetime of Unit. Which is wrong because your associated types depend on the lifetimes, I don’t think that you can provide a decent generic bound that restricts users to only use Unit until we get HRTB.

1 Like

With HRTB the correct bound would look something like this

impl BlackBox<dyn for<'a, R: 'a + Deref<Target = dyn Any>,
                          W: 'a + DerefMut<Target = dyn Any>,
                  > Unit<'a, Borrowed = R, MutBorrowed = W, Owned = Box<dyn Any>>>

But sadly you can’t do this yet, so instead you will have to just yet. So you will have to use the second more general impl, but still not specify Borrowed and MutBorrowed.

I would also recommend using these type aliases to make work easier

type Borrowed<'a, T: Unit<'a>> = <T as Unit<'a>>::Borrowed;
type MutBorrowed<'a, T: Unit<'a>> = <T as Unit<'a>>::MutBorrowed;

It will save a lot of typing and improve readablility for this section.
You will also have to start annotating lifetimes of &self to &'a self and using that lifetime in Borrowed<'a, U>/MutBorrowed<'a, U>

I sent a pull request with the fix

1 Like

Woah! Thanks for the pull request! It is very much appreciated!

1 Like