I'm once again stuck with trying to cram an async function into something. This time it's a type, which will be used in an enum later. But for the minimal example, which doesn't work, I have this code. As you can see, I don't manage to define a function with this type, and calling it fails, too.
Something to remember about trait objects (i.e. anything with a dyn) is that their actual size and type isn't known at runtime, so they always need to be behind a pointer of some sort.
The function itself is fine, because you've written Box<dyn Fn(...) -> ...>, however the return type says you are returning a dyn Future<...> by value. However, that's not possible because the compiler can't know how much space to set aside for the return value when it is stored on the stack.
I think you can fix this by returning Box<dyn Future<Output = String>>. There might also be something in the futures crate that takes an async function and returns a function which returns a boxed future.
The return types of async fn are opaque, unnameable types. Thus the need to type-erase the return value, as you have attempted to do with -> dyn Future<Output = String>.
However, dyn Trait types are dynamically sized, and they have to be behind some kind of pointer, e.g. in a Box. The opaque types I mentioned aren't boxed, so you'll need some sort of wrapper function to wrap up and coerce the opaque return types into boxes.
Compiling playground v0.0.1 (/playground)
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> src/lib.rs:18:52
|
18 | let f: IoFunc = Box::new(|input| Box::pin(conv.doit(input)));
| ^^^^
|
note: first, the lifetime cannot outlive the lifetime `'_` as defined here...
--> src/lib.rs:18:30
|
18 | let f: IoFunc = Box::new(|input| Box::pin(conv.doit(input)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that closure can access `conv`
--> src/lib.rs:18:47
|
18 | let f: IoFunc = Box::new(|input| Box::pin(conv.doit(input)));
| ^^^^
= note: but, the lifetime must be valid for the static lifetime...
note: ...so that the types are compatible
--> src/lib.rs:18:38
|
18 | let f: IoFunc = Box::new(|input| Box::pin(conv.doit(input)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: expected `Pin<Box<(dyn futures::Future<Output = String> + std::marker::Send + 'static)>>`
found `Pin<Box<dyn futures::Future<Output = String> + std::marker::Send>>`
For more information about this error, try `rustc --explain E0495`.
error: could not compile `playground` due to previous error
Do you need to call an IoFunc multiple times? If so, it isn't possible to make it work with &mut self. Every time you call f, that creates a new doit future, which borrows &mut conv. If multiple doit futures existed at the same time, that would create multiple &mut conv borrows, which isn't allowed. So a solution would require one of three things:
Make IoFunc a FnOnce
Use &self in doit, possibly with interior mutability
Change IoFunc to take the &mut Convert reference every time it is called
OK - again, once it's explained, it makes sense And I'm never sure whether my explanations are correct... So thanks a lot.
I need to call it multiple times. In my actual code I have another solution with using a trait, which works quite well. But I was hoping to make it nicer by using callbacks. Well, seems not so. Which is also a good result!