Hello everybody,
I've been trying to save something like the entry of request handlers, so that i can query and call them later.
to be simple, I got code like this:
// in request/mod.rs
struct RequestHandler {
name: &'static str,
func: WHAT_TYPE_IS_IT?,
}
// and then i want use it like this:
{
// let req = some json::JsonValue
let handler = get_handler("on_get_index");
handler.func(&req) // returning resp: json::JsonValue
}
so what type is the func field, to store functions like on_request?
I've been trying for hours, and I found that the on_request token is just like a type which MakeSomeFuture, and I cannot find out what I was asking.
async fn (...) -> Ret has a signature of fn(...) -> impl Future<Output = Ret>.
Given your inventory use case, generics are out of the question, so I'd say go for the following type:
fn (&'_ json::JsonValue) ->
Pin<Box<dyn // owned trait object
Future<Output = json::JsonValue> // future API / pollable
+ Send // required by non-single-threaded executors
+ '_ // may capture `req`, which is only valid for the `'_` lifetime
>>
If you do want to store the function producing the future, then it should be possible to have the type as Box<dyn Fn(Request) -> Box<dyn Future<Output = Ret>>. The key is that each async fn has a unique return type, so two async fns never even have the same function signature.
To fix this, you'll need to wrap your async fns in another function - maybe a closure, like |req| Box::new(func(req)). That way all the functions you store get a unified return type - Box<dyn Future<...>>, so you can store them all in the same structure.
Box<dyn Future<...>> is sadly not a future, in the sense that it cannot be polled given the Pin<&mut Self> receiver on Future::poll().
So you either add an Unpin bound (not recommended, it will make many async fns stop working), or you wrap the Box in a Pin< _ >, by using Box::pin() instead of Box::new().
By the way, if you are using #[async_trait] you shouldn't even be needing that wrapping since the proc macro does it automagically for you.
Also, you can directly define your async fns as:
type DynFut<'lt, T> = ::std::pin::Pin<Box<dyn 'lt + Send + ::std::future::Future<Output = T>>>;
// in request/on_get_index.rs
pub
fn on_request (req: &'_ json::JsonValue) -> DynFut<'_, json::JsonValue>
{
Box::pin(async move {
// body of the function
})
}
For simple function signatures, you can define that transformation with a macro_rules! macro:
// in request/on_get_index.rs
#[macro_rules_attribute(dyn_async!)]
pub
async fn on_request<'req> (req: &'req json::JsonValue) -> json::JsonValue
{
// body of the function
}
// ...
let func = on_request; // no need for the closure that `Box::pin`s!
Thank you so much for posting this. I was banging my head against this for ages, and getting sent in circles by the borrow-checker. In the end I stole your macro and type definition, and used it in https://github.com/jeremyandrews/goose/pull/8/files (very much work-in progress at the moment).
I have come across this "how do you specify the lifetime of an async function that borrows its arguments" problem before, when trying to write patches to gothamrouter.to_async(), simple async/.await examples by alsuren · Pull Request #281 · gotham-rs/gotham · GitHub . In gotham's case, they have a handler function type that takes ownership of a context object (State) and returns Box<dyn Future<Output = Result<(State, Response), (State, Err)>>. I managed to make a helper function that accepts an async function and boxes the result for you, but I never managed to go all the way and make a helper that allows you to borrow State.
Is there a way of specifying lifetimes that allows you to pass in a plain old async fn which borrows its arguments?