Info
This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Higher-rank trait bounds (HRTBs) with "outer" generic type parameter
Higher-rank trait bounds (HRTBs) with "outer" generic type parameter
โ Rust ๐ 2025-08-15 ๐ค surdeus ๐๏ธ 3I 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
๐ท๏ธ Rust_feed