[About HRTB] How to make this compile?

use futures::{Sink, never::Never, sink};

trait Foo {
    fn foo(&self) -> Box<dyn for<'a> Sink<&'a str, Error = Never>>;
}

struct A;

impl Foo for A {
    fn foo(&self) -> Box<dyn for<'a> Sink<&'a str, Error = Never>> {
        Box::new(sink::unfold(String::new(), async |mut s, i| {
            s += i;
            Ok(s)
        }))
    }
}

trait Bar {
    fn foo(&self) -> Box<dyn for<'a> Fn(&'a str)>;
}

struct B;

impl Bar for B {
    fn foo(&self) -> Box<dyn for<'a> Fn(&'a str)> {
        Box::new(|_: &str| {})
    }
}
cargo check
    Checking test_ v0.1.0 (/tmp/test_)
error: implementation of `FnOnce` is not general enough
  --> src/lib.rs:11:9
   |
11 | /         Box::new(sink::unfold(String::new(), async |mut s, i| {
12 | |             s += i;
13 | |             Ok(s)
14 | |         }))
   | |___________^ implementation of `FnOnce` is not general enough
   |
   = note: `{async closure@src/lib.rs:11:46: 11:62}` must implement `FnOnce<(String, &'1 str)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(String, &'2 str)>`, for some specific lifetime `'2`

I was told to let target type drive HRTB, so impl Bar for B can be compiled. But why it fails on impl Foo for A?

This compiles without HRTB:

use futures::{Sink, never::Never, sink};

trait Foo {
    fn foo(&self) -> Box<dyn Sink<&str, Error = Never> + '_>;
}

struct A;

impl Foo for A {
    fn foo(&self) -> Box<dyn Sink<&str, Error = Never> + '_> {
        Box::new(sink::unfold(String::new(), async |mut s, i| {
            s += i;
            Ok(s)
        }))
    }
}

trait Bar {
    fn foo(&self) -> Box<dyn for<'a> Fn(&'a str)>;
}

struct B;

impl Bar for B {
    fn foo(&self) -> Box<dyn for<'a> Fn(&'a str)> {
         Box::new(|_: &str| {})
    }
}

I think you need to make the lifetime in the Sink to be independent of that of self.

True. And it's exactly why I use Pin<Box<dyn for<'a> Sink<Box<DynMessage<'a>>, Error = Error> + Send + 'static>>, as return type :sob:

The issue seems to be here in this implementation:

impl<T, F, R, Item, E> Sink<Item> for Unfold<T, F, R>
where
    F: FnMut(T, Item) -> R,
    R: Future<Output = Result<T, E>>

I cannot see a way to control the type of the closure from the Sink trait's type parameters.

1 Like

Please post text, not screenshots.

I simplified the code to this:

use futures::{Sink, never::Never, sink};

trait Foo {
    fn foo(&self) -> Box<dyn for<'a> Sink<&'a str, Error = Never>>;
}

struct A;

impl Foo for A {
    fn foo(&self) -> Box<dyn for<'a> Sink<&'a str, Error = Never>> {
        Box::new(sink::drain())
    }
}

And with RUSTFLAGS="-Znext-solver=globally" cargo check, I got a more acceptable reason for compile error:

RUSTFLAGS="-Znext-solver=globally" cargo check
    Checking test_ v0.1.0 (/run/media/louis/Work/rust/test_)
error[E0277]: the trait bound `for<'a> futures::sink::Drain<&str>: futures::Sink<&'a str>` is not satisfied in `futures::sink::Drain<&str>`
  --> src/lib.rs:11:9
   |
11 |         Box::new(sink::drain())
   |         ^^^^^^^^^^^^^^^^^^^^^^^ within `futures::sink::Drain<&str>`, the trait `for<'a> futures::Sink<&'a str>` is not implemented for `futures::sink::Drain<&str>`
   |
help: the trait `Sink<&'a _>` is not implemented for `futures::sink::Drain<&str>`
      but trait `Sink<&_>` is implemented for it
  --> /home/louis/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-util-0.3.32/src/sink/drain.rs:41:1
   |
41 | impl<T> Sink<T> for Drain<T> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: required because it appears within the type `futures::sink::Drain<&str>`
  --> /home/louis/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-util-0.3.32/src/sink/drain.rs:11:12
   |
11 | pub struct Drain<T> {
   |            ^^^^^
   = note: required for `Box<futures::sink::Drain<&str>>` to implement `CoerceUnsized<Box<dyn for<'a> futures::Sink<&'a str, Error = Infallible>>>`
   = note: required for the cast from `Box<futures::sink::Drain<&str>>` to `Box<(dyn for<'a> futures::Sink<&'a str, Error = Infallible> + 'static)>`

For more information about this error, try `rustc --explain E0277`.

So in compiler's opinion, Sink<&'any _> is not implemented for futures::sink::Drain<&str>, while Fn(&'any _) is implemented for impl Fn(&str) :sob: That's strange...

Maybe waiting Rust update is the solution :sob: :sob: :sob:

Thanks for posting text :slight_smile: .

Unfold<T, F, R> won't work because there's always a one-to-one relationship between F and Item. Both in the bounds @firebits.io pointed out and on the unfold function:

pub fn unfold<T, F, R, Item, E>(init: T, function: F) -> Unfold<T, F, R>
where
    F: FnMut(T, Item) -> R,
    R: Future<Output = Result<T, E>>,

You want something like F: for<'a> FnMut(T, &'a str) -> Future<'_> where the second argument can vary by lifetime and be captured by the future. But &strs with different lifetimes are different types, and Item must be a single type. (No Rust update is going to change this part.)

Even if you work around that with your own Unfold, the next problem is that your future captures the input, so your returned futures need to vary by lifetime too. But compiler generate futures, like what is returned from your async |..|, don't have names, so you can't actually write out something like -> Future<'_> in the bound. This is a pretty common pain point in Rust async. The usual work around is to use type erasure (Pin<Box<dyn Future ...>>). (Some Rust update may improve this part. But no time soon I imagine.)

However.... my guess is that's not applicable here anyway, based on the Sink trait:

pub trait Sink<Item> {
    fn start_send(self: Pin<&mut Self>, item: Item) -> Result<(), Self::Error>;
    fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
    // ...
}

At least not for things working how they are supposed to in the async world: you would need to generate the future in start_send and poll it in poll_ready, but if Item contains a lifetime which Self does not, there's nothing to link the calls together -- nothing to keep the borrow in the Item active at the call site.

You could instead block the future, but the better solution IMO is to not have futures here in the first place and be up-front about what the implementation does. Here's a stab at that.

2 Likes

Amazing clear answer! Thanks a lot :folded_hands: