我有一个函数
fetch_stuff
,它需要对 sqlx 连接的可变引用。如果这个函数失败,我想重试N次。我需要对许多类似的函数进行这种重试行为,因此我尝试制作一个通用的 retry_up_to_n_times
函数,它采用异步 fnmut 和允许的重试次数作为输入。
#[derive(Clone)]
pub struct App {
db_pool: sqlx::PgPool,
}
impl App {
pub async fn foo(&self) {
let mut db_conn = self.db_pool.acquire().await.unwrap();
retry_up_to_n_times(|| fetch_stuff(&mut db_conn), 3).await;
}
}
pub async fn fetch_stuff(db_conn: &mut sqlx::postgres::PgConnection) -> Result<(), StorageError> {
//do stuff with db_conn
Ok(())
}
pub async fn retry_up_to_n_times<F, T, O, E>(mut func: F, max_tries: usize) -> T::Output
where
F: FnMut() -> T,
T: std::future::Future<Output = Result<O, E>>,
E: Error
{
let mut fail_count = 0;
loop {
match func().await {
Ok(t) => return Ok(t),
Err(e) => {
fail_count += 1;
if fail_count >= max_tries {
return Err(e);
}
}
}
}
}
编译器给了我这个错误
error: captured variable cannot escape `FnMut` closure body
--> src/app/tag.rs:32:32
|
31 | let mut db_conn = self.db_pool.acquire().await.unwrap();
| ----------- variable defined here
32 | retry_up_to_n_times(|| fetch_stuff(&mut db_conn), 3).await;
| - ^^^^^^^^^^^^^^^^^-------^
| | | |
| | | variable captured here
| | returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
| inferred to be a `FnMut` closure
|
= note: `FnMut` closures only have access to their captured variables while they are executing...
= note: ...therefore, they cannot allow references to captured variables to escape
我对 Rust 没有太多经验,所以我不确定我是否完全理解它。我已经阅读了有关该主题的所有内容,并尝试使用 Rc 和 RefCell,但没有成功。 我也尝试过使用 tokio_retry,但最终遇到了同样的问题。
我想做的事情可能吗?
最简单的方法是在闭包中获取池连接。无论如何,这可能就是您想要的;如果您正在使用的连接断开并导致错误,您应该在下次重试时获取另一个连接,否则由于连接断开,它注定会反复失败。
pub async fn foo(&self) {
retry_up_to_n_times(
|| async {
let mut db_conn = self.db_pool.acquire().await.unwrap();
fetch_stuff(&mut db_conn).await
},
3,
)
.await;
}
(理想情况下,您会返回错误而不是
unwrap()
ping 它,但我会将其作为练习留给您。)
为了解释为什么不允许这样做,
FnMut
并不能阻止代码在第一个 Future
被删除之前第二次调用它。这将导致两个 future 实际上具有相同的值,这违反了 Rust 的别名规则。因此,&mut
可能不会返回包含捕获值的独占借用的值。一个简化的示例说明了 Rust 试图防止的问题:
FnMut