async fn init_stuff() -> u8 {
println!("INIT STUFF");
1
}
async fn fetch() -> u8 {
println!("FETCH DATA");
2
}
async fn cleanup() {
println!("CLEANUP");
}
async fn within_transaction(x: fn(u8)) {
let conn_id = init_stuff().await;
x(conn_id);
cleanup().await;
}
#[tokio::test]
async fn testing() {
within_transaction(|conn_id| {
println!("--> conn_id={}", conn_id);
assert_eq!(1, conn_id);
let res = fetch().await; // this doesn't work since the closure isn't async
assert_eq!(2, res);
})
.await;
}
This is so I don't have to worry about forgetting to call the cleanup function. It's a pretty common pattern overall so I'd like to make it work.
I've been banging my head on this for a few hours, however I see I'm stuck so I'll have to improve my Rust knowledge before finding the answer on my own.
An async function is really just a regular function that returns something that implements std::future::Future, so first you'll want to make x an async function:
async fn within_transaction<F>(x: fn(u8) -> F)
where
F: Future<Output = ()>,
{
let conn_id = init_stuff().await;
x(conn_id).await;
cleanup().await;
}
As for the closure, we can make the closure return a future using an async block, essentially making it an async function:
within_transaction(|conn_id| async move {
println!("--> conn_id={}", conn_id);
assert_eq!(1, conn_id);
let res = fetch().await;
assert_eq!(2, res);
})
.await;
We need the move in addition to async to move conn_id into the async block, without it we get an error similar to what you might get if you forget a move before a closure that needs it:
error[E0373]: async block may outlive the current function, but it borrows `conn_id`, which is owned by the current function
--> src/lib.rs:30:40
|
30 | within_transaction(|conn_id| async {
| ________________________________________^
31 | | println!("--> conn_id={}", conn_id);
| | ------- `conn_id` is borrowed here
32 | | assert_eq!(1, conn_id);
33 | |
34 | | let res = fetch().await;
35 | | assert_eq!(2, res);
36 | | })
| |_____^ may outlive borrowed value `conn_id`
|
note: async block is returned here
--> src/lib.rs:30:34
|
30 | within_transaction(|conn_id| async {
| __________________________________^
31 | | println!("--> conn_id={}", conn_id);
32 | | assert_eq!(1, conn_id);
33 | |
34 | | let res = fetch().await;
35 | | assert_eq!(2, res);
36 | | })
| |_____^
help: to force the async block to take ownership of `conn_id` (and any other referenced variables), use the `move` keyword
|
30 | within_transaction(|conn_id| async move {
| ++++
For more information about this error, try `rustc --explain E0373`.
This is because you're trying to express that x has to be "something" that is a closure (or function) that takes a u8 and returns a Future with no value. Future is a trait, not a "materialized" type (a struct or enum), so the compiler needs to be told that you want something that returns Future, but that is determined by whoever calls within_transaction.
I'm assuming you ended up with code that looks like this: Rust Playground