Em uma caixa, tenho um wrapper de uso geral para arquivos temporários:
pub struct TempFile {
// ...
}
Em outra caixa, tenho um wrapper de repetição assíncrona de uso geral:
pub async fn retry<F, R>(mut func: F) -> Result<(), String>
where
F: FnMut() -> R,
R: Future<Output = Result<(), String>>
{
for _ in 0..5 {
if func().await.is_ok() {
return Ok(());
}
}
Err("failed".to_owned())
}
Na terceira caixa, tenho uma função de download de uso geral que baixa para um arquivo temporário:
async fn download(output: &mut TempFile) -> Result<(), String> {
// ...
}
Agora quero adicionar uma função de nova tentativa de download como esta:
async fn download_with_retry(output: &mut TempFile) -> Result<(), String> {
retry(|| { download(output) }).await
}
Entretanto, isso não compila:
error: captured variable cannot escape `FnMut` closure body
--> src/lib.rs:16:20
|
15 | async fn download_with_retry(output: &mut TempFile) -> Result<(), String> {
| ------ variable defined here
16 | retry(|| { download(output) }).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
Entendo por que isso está acontecendo: embora a retry
função sempre espere func
terminar antes de invocá-la novamente, o compilador não consegue ver isso e tem que assumir que múltiplas invocações paralelas são possíveis, levando a múltiplos empréstimos mutáveis simultâneos de output
.
Sei que posso contornar isso passando Rc<RefCell<TempFile>>
para download_with_retry
, mas isso significa que ele vaza para a API pública dessa função sem nenhum bom motivo.
Existe outra solução? Idealmente, modificando apenas a implementação de retry
e/ou download_with_retry
, mantendo as assinaturas?
A assinatura não pode permanecer a mesma. O problema é sugerido no erro: se uma closure captura
&mut
, você não pode usar isso&mut
em umFuture
retorno, exceto movendo o&mut
para fora das capturas (tornando a função aFnOnce
, ou usandoOption::take()
ou algo assim). Em geral,Fn
s eFnMut
s não podem "emprestar": retornar coisas que tomam emprestado de si mesmas.O Rust 1.85 adicionou características de função assíncronas e fechamentos assíncronos para permitir isso. Para usá-los, altere a assinatura de
retry()
parae o uso para
No entanto, atualmente falta uma funcionalidade para expressar
Send
limites em funções assíncronas futuras, então talvez você não consiga usar isso. Esperamos que os recursos relevantes sejam implementados e estabilizados em breve; a notação de tipo de retorno faz parte da solução.