rustreferencetraitslifetimetype-alias

How, in Rust, to write a trait that expresses the ability to create of an object that lives as long as a reference to self?


I'm losing my mind a little bit.

I want to express a trait that guarantees the ability of a type to create another type "Item" from a reference (&'a self) that lives as long as the reference ('a), but all the versions of that type that I can successfully implement seem to fail in generic functions.

I'll provide simplified versions of different factorings I tried that in my testing all generate the same errors as my full code. Tldr at bottom.

My preferred way to express this in code is:

    trait T {
        type Item<'a> where Self: 'a;
        type Error;
        fn f<'a>(&'a mut self) -> Result<Self::Item<'a>, Self::Error>;
    }

because it contains the lifetime to the places where it matters and doesn't pollute the error type (or any other data) with unnecessary lifetime coloring. (If I have a place in code that just uses trait T for the Error type, I don't want to add a pointless lifetime).

This code is easy to implement successfully (which I'll demonstrate with a dummy type)

    struct S(i32)
    impl T for S {
        type Item<'a> = &'a i32;
        type Error = Infallible;
        fn f<'a>(&'a mut self) -> Result<Self::Item<'a>, Self::Error> {
            Ok(&self.0)
        }
    }

but it has a weird hang-up in generics. This works:

    fn g<X: T>(x: X)->Option<()> {
        let item = x.f().ok()?;
        Some(())
    }

but specifying the item doesn't:

    fn f<'a, X: T<Item<'a> = &'a i32> + 'a>(x: X)->Option<()> {
        let item = x.f().ok()?;
        let y = item + 1;
        Some(())
    }

--argument requires that x is borrowed for `'a"

--x dropped here [the end of the fn] while still borrowed [no, it's not?]

I don't really know why this one doesn't work

This factoring works in the generic, but isn't possible to implement as the lifetime "'a" which is needed to describe the type "Item" is not constrained by trait or impl

    trait U {
        type Item;
        type Error;
        fn f(&'a mut self) -> Result<Self::Item, Self::Error>;
    }

Factorings which move the lifetime and/or Item type to the trait params have the same problem as "T" if they include the 'a lifetime in the "f" signature, but the same problem as "U" otherwise.

    trait V1<'a, Item> {
        type Error;
        fn f(&'a self) -> Result<Item, Self::Error>;
    } //Same as T
    trait V2<'a, Item> {
        type Error;
        fn f(&self) -> Result<Item, Self::Error>;
    } //Same as U

You might have noticed that this all is very similar to a "TryFrom" and wonder if I tried expressing the trait as a variant of that. I did.

    trait W<'a> where Self: 'a {
        type Item: TryFrom<&'a Self>;
        fn f(&'a self) -> Result<Self::Item, <Self::Item as TryFrom<&'a Self>>::Error>
            {<Self::Item as TryFrom<&'a Self>>::try_from(self)} 
    }

This is implementable, but it gives a really weird error in the fn "f".

    fn f<'a, X: W<'a, Item = &'a i32> + 'a>(x: X)->Option<()>
    {
        let item = x.f().ok()?;
        let y = item + 1;
        Some(())
    }

--the trait bound &'a i32: From<&'a X> is not satisfied [which bound is both unnecessary and guaranteed to be implemented.]

--the trait From<&'a X> is not implemented for &'a i32, which is required by &'a i32: TryFrom<&'a X> [no it's not and can't be?] (I also tried adding that bound explicitly to the params but it still gave the same error.)

I'm lost. Particularly, I don't get the errors for traits T and W, which are the most sensibly constructed, as opposed to U, V1, and V2 which are really just hackjobs. I also made 4 other refactorings which I didn't include. It should be noted that this trait is basically an open-ended version of a iterator trait ("I") I made earlier such that I<Item = T> is equivalent to T<'a, Item = I::Iter<'a>>. And that had no problems because the generic form makes no references to the lifetime "'a".


Solution

  • This can be fixed with a higher-ranked trait bound.

    pub fn f<X: for<'a> T<Item<'a> = &'a i32>>(mut x: X) -> Option<()> {
        let item = x.f().ok()?;
        let y = item + 1;
        Some(())
    }
    

    Normal lifetime arguments can only belong to references from outside the function, but the &mut self here is contained entirely within the function. There's no way for the caller to provide this lifetime, so instead, the HRTB requires X implements T for every lifetime.