Higher-rank trait bounds (HRTBs) with "outer" generic type parameter

โš“ Rust    ๐Ÿ“… 2025-08-15    ๐Ÿ‘ค surdeus    ๐Ÿ‘๏ธ 3      

surdeus

I will start from afarโ€ฆ

Let's assume we have a producer-like trait:

trait Producer {
    type Product;
    fn produce(&self) -> Self::Product;
}

(in the actual code the trait is more complex, with more parameters in product and restrictions on Product, but let's keep it as simple as possible).

And we want to make a mapping adapter (that applies a certain mapping function to the products of a certain base producer and so satisfies Producer):

// Pseudocode:
fn โ€ฆ(base: impl Producer, mapper: impl Fn(โ€ฆ) -> โ€ฆ) -> impl Producer {โ€ฆ}

That's easy to implement:

struct MappingAdapter<
    Base: Producer,
    Mapper: Fn(Base::Product) -> FinalProduct,
    FinalProduct
> {base: Base, mapper: Mapper}
impl<
    Base: Producer,
    Mapper: Fn(Base::Product) -> FinalProduct,
    FinalProduct
> Producer for MappingAdapter<Base, Mapper, FinalProduct> {
    type Product = FinalProduct;
    fn produce(&self) -> Self::Product {(self.mapper)(self.base.produce())}
}

Let's test it:

#[test]
fn test() {
    struct CopyingProducer {gold: usize}
    impl Producer for CopyingProducer {
        type Product = usize;
        fn produce(&self) -> usize {self.gold}
    }

    let adapted_producer = MappingAdapter{
        base: CopyingProducer{gold: 42},
        mapper: |v|(v, v)
    };
    assert_eq!(adapted_producer.produce(), (42, 42))
}

But here's where the question becomes slightly more tricky. What if we want the products of our producer to be possibly dependent on it (e.g. if the producer contains some golden value and references to it are the products)? It's easy to update the Producer trait:

trait Producer {
    type Product<'a> where Self: 'a;
    fn produce<'a>(&'a self) -> Self::Product<'a>;
}

Also, at first glance, it seams to be easy to update the MappingAdaptor:

struct MappingAdapter<
    Base: Producer,
    Mapper: Fn(Base::Product<'_>) -> FinalProduct,
    FinalProduct
> {base: Base, mapper: Mapper}
impl<
    Base: Producer,
    Mapper: Fn(Base::Product<'_>) -> FinalProduct,
    FinalProduct
> Producer for MappingAdapter<Base, Mapper, FinalProduct> {
    type Product<'a> = FinalProduct where Self: 'a;
    fn produce<'a>(&'a self) -> Self::Product<'a> {
        (self.mapper)(self.base.produce())
    }
}

But actual test won't work:

#[test]
fn test() {
    struct Obj {}
    struct ReferencingProducer {gold: Obj}
    impl Producer for ReferencingProducer {
        type Product<'a> = &'a Obj where Self: 'a;
        fn produce<'a>(&'a self) -> &'a Obj {&self.gold}
    }

    fn pair<'a>(value: &'a Obj) -> (&'a Obj, &'a Obj) {(value, value)}
    let adapted_producer = MappingAdapter{
        base: ReferencingProducer{gold: Obj{}},
        mapper: pair
    };
    assert_eq!(
        adapted_producer.produce(),
        //               ^^^^^^^ method cannot be called
        //                       due to unsatisfied trait bounds
        // trait bound `<for<'a> fn(&'a Obj) -> (&'a Obj, &'a Obj) {pair}
        // as FnOnce<(&Obj,)>>::Output = (&Obj, &Obj)` was not satisfied
        (&adapted_producer.base.gold, &adapted_producer.base.gold)
    )
}

If I understand correctly, that's because Mapper: Fn(Base::Product<'_>) -> FinalProduct, which is a shorthand for Mapper: for <'t> Fn(Base::Product<'t>) -> FinalProduct, is able to represent only functions like <'a>(&'a usize) -> usize but not like <'a>(&'a usize) -> &'a usize. I would write something like

Mapper: for <'t> Fn(Base::Product<'t>) -> FinalProduct where FinalProduct: 't`,
//                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

but that syntax is simply not allowed. Any ideas?

1 post - 1 participant

Read full topic

๐Ÿท๏ธ Rust_feed