Is `impl Trait` inside trait definition of a function possible?

I wanted to express a trait like

trait WriterFn<'b> =
    FnOnce(PathBuf) -> impl Future<Output = Result<(), io::Error>> + Send + 'b;

Does anybody know if that is already somehow possible in Rust nightly as of today?
I only came across this RFC: 2515-type_alias_impl_trait - The Rust RFC Book

As a starting point:

trait WriterFn: FnOnce(PathBuf) -> Self::Fut {
    type Fut: Future<Output = Result<(), io::Error>>;
}

impl<F: ?Sized + FnOnce(PathBuf) -> Fut, Fut: Future<Output = Result<(), io::Error>>> WriterFn for F {
    type Fut = Fut;
}

Regarding the Send + 'b, I’m not sure if you want that one to apply to the closure or to the future.


Assuming it’s the future, then it becomes something like

trait WriterFn<'b>: FnOnce(PathBuf) -> Self::Fut {
    type Fut: Future<Output = Result<(), io::Error>> + Send + 'b;
}

impl<'b, F: ?Sized, Fut> WriterFn<'b> for F
where
    F: FnOnce(PathBuf) -> Fut,
    Fut: Future<Output = Result<(), io::Error>> + Send + 'b,
{
    type Fut = Fut;
}
2 Likes

Awesome: Is the test function als be able to live as a method in a trait, making it a async trait?
Or does the example become very different?

I don’t see any reason why not. But just try it, and if problems occur, feel free to share them.


Edit: Perhaps in a trait method, you’ll want to have it declare the method as fn (…) -> impl Future<Output = …> + Send so it can be used generically in contexts where futures calling that method need to be spawned. In that case, the method might in turn need a Send bound on the closure, too, i.e. F: WriterFn<'…> + Send – or if this comes up commonly, you can consider making a Send bound on the closure part of the definition, too (putting it into the supertrait bounds next to FnOnce)

1 Like

Works: Rust Playground

but in a more elaborate scenario with Rocket.
I get a strange cycle detected error in the compiler I have no idea what it means:

Example

The cycle error seems fixed on nightly. Maybe it was the same one as query cycle: `cycle detected when checking effective visibilities` · Issue #119346 · rust-lang/rust · GitHub

Your code example then runs into object safety errors

error[E0038]: the trait `BlobStorage2` cannot be made into an object
  --> src/main.rs:52:7
   |
52 |     i.store_blob(|r: PathBuf| async move { p.copy_to(r).await })
   |       ^^^^^^^^^^ `BlobStorage2` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/main.rs:35:14
   |
34 | trait BlobStorage2: Sync + Send {
   |       ------------ this trait cannot be made into an object...
35 |     async fn store_blob<'a, 'b>(&'a self, writer: impl WriterFn<'b>) -> bool {
   |              ^^^^^^^^^^
   |              |
   |              ...because method `store_blob` is `async`
   |              ...because method `store_blob` has generic type parameters
   = help: consider moving `store_blob` to another trait
   = help: only type `Storage` implements the trait, consider using it directly instead

error[E0038]: the trait `BlobStorage2` cannot be made into an object
  --> src/main.rs:52:5
   |
52 |     i.store_blob(|r: PathBuf| async move { p.copy_to(r).await })
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `BlobStorage2` cannot be made into an object
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/main.rs:35:14
   |
34 | trait BlobStorage2: Sync + Send {
   |       ------------ this trait cannot be made into an object...
35 |     async fn store_blob<'a, 'b>(&'a self, writer: impl WriterFn<'b>) -> bool {
   |              ^^^^^^^^^^
   |              |
   |              ...because method `store_blob` is `async`
   |              ...because method `store_blob` has generic type parameters
   = help: consider moving `store_blob` to another trait
   = help: only type `Storage` implements the trait, consider using it directly instead
…
…
…

and if I resolve these by not using a trait object, other errors come up, too (I’m going to have to stop at some point :sweat_smile: )

Autsch ok the has generic parameters makes sense (C++: you cannot use templates with virtual methods): So I read in a blog post about that generics in traits are not possible, but it was not clearly stated, I just took away that the compiler just needs to generate all sorts of possible vtable entries.
The compiler should know the type of writer and just construct a vtable entry for every used such impl WriterFn, I hoped for that, but apperently it is not possible. Maybe some Rust compiler expert can shine some light into this.

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.