How to specify a HRTB for dyn Fn in my own trait?

I'm building a pipelining tool, and this is my main trait:

trait Transform<A, B> {
    fn transform(&self, input: A, out: &dyn Fn(B));
}

which works out nicely for owned values, e.g.

struct Repeat { times: usize }

impl<A: Clone> Transform<A, A> for Repeat {
    fn transform(&self, input: A, out: &dyn Fn(A)) {
        std::iter::repeat(input)
            .take(self.times)
            .for_each(|v| (out)(v))
    }
}

This does not work if B is a borrow of A.

In this contrived example (a more realistic case is transforming a Read into tar::Entry<'a, R>..):

use std::io::{Stdin, StdinLock};

struct Lock { }

impl Transform<Stdin, StdinLock> for Lock {
    fn transform(&self, input: Stdin, out: &dyn Fn(StdinLock)) {
        (out)(input.lock())
    }
}
error[E0053]: method `transform` has an incompatible type for trait
  --> min.rs:25:5
   |
4  |     fn transform(&self, input: A, out: &dyn Fn(B));
   |     ----------------------------------------------- type in trait
...
25 |     fn transform(&self, input: Stdin, out: &dyn Fn(StdinLock)) {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected bound lifetime parameter, found concrete lifetime
   |
   = note: expected fn pointer `fn(&Lock, std::io::Stdin, &dyn std::ops::Fn(std::io::StdinLock<'_>))`
              found fn pointer `fn(&Lock, std::io::Stdin, &dyn for<'r> std::ops::Fn(std::io::StdinLock<'r>))`

Fair enough, the following works:

impl Lock {
    // desugars into
    // fn transform(input: Stdin, out: &dyn for<'r> Fn(StdinLock<'r>))
    fn transform(input: Stdin, out: &dyn Fn(StdinLock)) {
        (out)(input.lock())
    }
}

I don't know how I can include this higher-rank trait bound in my trait. I've tried, to no avail:

trait TransformTmp<'r, A, B: 'r> {
    fn transform(input: A, out: &dyn Fn(B));
}

trait TransformTmpAll<A, B>: for<'r> TransformTmp<'r, A, B> {}
32 | impl<'r> TransformTmp<'r, Stdin, StdinLock<'r>> for Lock {
   |      -- lifetime `'r` defined here
33 |     fn transform(input: Stdin, out: &dyn Fn(StdinLock<'r>)) {
34 |         (out)(input.lock())
   |         ------^^^^^--------
   |         |     |
   |         |     borrowed value does not live long enough
   |         argument requires that `input` is borrowed for `'r`
35 |     }
   |     - `input` dropped here while still borrowed

You can't with the given interface. You'll have to lift the Fn to a generic parameter.

trait Transform<A, F> {
    fn transform(&self, input: A, out: F);
}

impl Transform<Stdin, &dyn Fn(StdinLock)> for Lock {...}

I would just use generics instead of a trait object, and maybe FnOnce instead of Fn

impl<F: Fn(StdinLock)> Transform<Stdin, F> for Lock {...}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.