rust define function that invokes async closure?

Viewed 522

Lets assume we have some repetitive async code that does htttp calls, and now we want to write a function that will take that same code as closure and record everything before the request and after the request.

Here is the simplified code that i want to achieve in kotlin (rust experts just look below the rust code ignore kotlin example)

suspend fun <A, B> logRequest(req: A, callable: suspend (A) -> B): B {
   println("write down the request $req")
   val rep: B = callable(req)
   println("write down the response $rep")
   return rep
}

this is a smiple function that accepts a request of type A and a closure that will take that same A do some async logic and return some B... note that closure is suspendable (async)

and this would be used like this

   val request = "REQUEST"
   val response = logRequest(request) {
      println("my closure uses '$it' and can do some async stuff")
      delay(1.second) // that is async
      "RESPONSE"
   }

if you run that you'll get output

write down the request REQUEST
my closure uses 'REQUEST' and can do some async stuff
write down the response RESPONSE

now trying to do the exact same thing with rust (i'm new)

use std::fmt::Display;

// ignore this
async fn delay(millis: i64) {}

fn log_request<A: Display, B: Display>(req: A, callable: impl FnOnce(A) -> B) -> B {
    println!("write down the request {}", &req);
    let rep: B = callable(req);
    println!("write down the response {}", &rep);
    rep
}

#[tokio::main]
async fn main() {
    let request = "REQUEST";

    let callable = |it: &str| -> &str {
        println!("my closure uses '{}' and can do some async stuff", it);
        // delay(1000).await; // uncommenting this
        "RESPONSE"
    };

    let response = log_request(request, callable);
}

what is the correct way to do this in rust?

if you uncomment the delay invocation compiler will say |it: &str| -> &str { is not async so how do you define it as async... since i'm already running in async scope i should surely be able to invoke async closures (which are equivalent to suspendable functions in kotlin)

1 Answers

The difference between the Kotlin and Rust example is that your Kotlin log_request is suspend which I assume is the same as an async fn in Rust. You can get what you want by making log_request in Rust async, too. This requires some additional step to await the Future in log_request and some tweaks to the function definition.

After adding the async on log_request, you need to change the output of your closure to something that returns a Future<Output=C> where C: Display. Then you need to await the output of the closure to actually get the displayable value.

The other change is within the closure definition itself, since the closure is not async, you need to work around this by returning a Future from it. Using an async {} block is the solution here.

These steps invalidate some of your type ascriptions, e.g. the &str ascriptions on the closures can be erased entirely and if you want to keep the rep: B annotation, that needs to be changed to rep: C.

use std::fmt::Display;
use std::future::Future;

// ignore this
async fn delay(millis: i64) {}

async fn log_request<A: Display, B, C: Display>(req: A, callable: impl FnOnce(A) -> B) -> C
where
    B: Future<Output = C>,
{
    println!("write down the request {}", &req);
    let rep: C = callable(req).await;
    println!("write down the response {}", &rep);
    rep
}

#[tokio::main]
async fn main() {
    let request = "REQUEST";

    let callable = |it| {
        async move {
            println!("my closure uses '{}' and can do some async stuff", it);
            delay(1000).await; // uncommenting this
            "RESPONSE"
        }
    };

    let response = log_request(request, callable).await;
}

Playground Link

Related