rust 当内部可变对象的生存期较短时,如何通过函数指针传递可变对象引用?[副本]

dgiusagp  于 2023-06-30  发布在  其他
关注(0)|答案(2)|浏览(104)

此问题已在此处有答案

Calling a generic async function with a (mutably) borrowed argument(1个答案)
4天前关闭。
我正在尝试编写一个函数,它接受一个指向一个函数的指针,该函数执行嵌套在事务中的一些SQL查询。这一天我一直在犯错误,我不知道如何解决。

最小可复制示例

Rust Playground Link

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)
}

如果你按原样运行代码,它会显示一个错误:

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

使用一些生命周期注解,我可以得到一个不同的错误-这是:

error[E0521]: borrowed data escapes outside of async closure
error[E0499]: cannot borrow `tx` as mutable more than once at a time

这个错误来自非玩具程序,但本质上指向let result = query_fn(&mut tx).await;,并声称对tx的可变引用无效。
在真实的的程序中,我也试图使这个通用的返回类型,但这是问题的核心。
注意:我使用sqlx进行查询,因此结构为QueryTransaction
我希望能够编写query_as_user函数来接受任何Query+执行方法(例如返回一行、多行、无...)。它应该使用查询函数中定义的方法执行查询,该方法嵌套在设置然后重置用户角色的事务中。

omhiaaxx

omhiaaxx1#

找到了一个使用async traits的方法。
使用实际的sqlx类型显示:

trait AsUserQueryFn<Args, Out> {
    async fn call(
        &mut self,
        tx: &mut Transaction<'_, Postgres>,
        args: Args,
    ) -> Result<Out, sqlx::Error>;
}

async fn execute_user_level_query<'a, Args, Out>(
    &'a self,
    as_user: UserTableRow,
    input: Args,
    mut execution_fn: impl AsUserQueryFn<Args, Out>,
) -> Result<Out, UserLevelQueryError> {
  ...
}
8ehkhllq

8ehkhllq2#

问题在于不能用参数的生命周期来标记查询函数的输出。做这样的事情会很好。

async fn query_as_user<Fut>(query_fn: for<'a> fn(&'a mut Transaction) -> Fut) -> usize 
where
  Fut: Future<Output = usize> + 'a

但这是不成立的。我做了一个需要盒装trait对象的解决方案。(playground)

use core::pin::Pin;role.
async fn query_as_user<F>(query_fn: F) -> usize
where
    F: for<'a> FnOnce(&'a mut Transaction) -> Pin<Box<dyn Future<Output = usize> + 'a>>,
{
    // same stuff
}

fn select_all(tx: &mut Transaction) -> Pin<Box<dyn Future<Output = usize> + '_>> {
    Box::pin(Query("SELECT * FROM table;").execute_with_return(tx))
}

我还从fn升级到了FnOnce,因为FnOnce更通用。您可能需要FnMutFn。您可能还需要将SendSync添加到trait对象。请注意,按照常规的生存期规则,完全不可能将生成的future放入tokio::spawn中。

相关问题