I am trying to write a function which takes a pointer to a function which executes some SQL query nested in a transaction. I have been running into errors with this for a day now and I do not know how to solve it.
Minimum Reproducible Example
use core::future::Future;
// Allows execution of a set of queries.
struct Transaction(pub usize);
// A single SQL query string.
struct Query(pub &'static str);
impl Query {
// Execute the query without returning anything.
pub async fn execute_without_return(&self, tx: &mut Transaction) {
tx.0 += 1;
println!("{}", self.0);
}
// Execute the query and return a result.
pub async fn execute_with_return(&self, tx: &mut Transaction) -> usize {
tx.0 += 1;
println!("{}", self.0);
return 123;
}
}
// Execute some query between two other queries to set and reset the user role.
async fn query_as_user<Fut>(query_fn: fn(&mut Transaction) -> Fut) -> usize
where
Fut: Future<Output = usize>
{
let mut tx = Transaction(0);
Query("SET ROLE user;").execute_without_return(&mut tx).await;
let result = query_fn(&mut tx).await;
Query("RESET ROLE;").execute_without_return(&mut tx).await;
result
}
async fn select_all(tx: &mut Transaction) -> usize {
Query("SELECT * FROM table;").execute_with_return(tx).await
}
#[tokio::main]
async fn main() {
let res = query_as_user(select_all).await;
println!("\nResult: {}", res)
}
If you run the code as is, it will show an error:
error[E0308]: mismatched types
--> src/main.rs:41:29
|
41 | let res = query_as_user(select_all).await;
| ------------- ^^^^^^^^^^ one type is more general than the other
| |
| arguments to this function are incorrect
|
= note: expected fn pointer `for<'a> fn(&'a mut Transaction) -> _`
found fn item `for<'a> fn(&'a mut Transaction) -> impl Future<Output = usize> {select_all}`
= note: when the arguments and return types match, functions can be coerced to function pointers
note: function defined here
--> src/main.rs:24:10
|
24 | async fn query_as_user<Fut>(query_fn: fn(&mut Transaction) -> Fut) -> usize
| ^^^^^^^^^^^^^ -------------------------------------
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to previous error
With some lifetime annotation finagling I can get a different error - which is:
error[E0521]: borrowed data escapes outside of async closure
error[E0499]: cannot borrow `tx` as mutable more than once at a time
This error is coming out of the non-toy program but is essentially pointing to let result = query_fn(&mut tx).await;
and claiming that the mutable reference to tx
is not valid.
In the real program I am also trying to make this general over the return type, but this is the core of the problem.
Note: I am using the sqlx
to make the queries, hence the structure of Query
and Transaction
.
I am expecting to be able to write the query_as_user
function to accept any Query
+ execution method (e.g. return one row, multiple rows, nothing...). It should execute the query using the method defined in the query function, nested in the transaction which sets then resets the user role.