Eu me deparei com um erro surpreendente (para mim) ao usar RefCell e quero entender melhor por que isso está acontecendo. Eu tinha algo parecido com este código abaixo, onde eu tenho um bloco while let consumindo uma função mutável em um RefCell emprestado:
struct Foo {
val: i32,
}
impl Foo {
fn increment(&mut self) -> Option<i32> {
if self.val >= 10 {
return None;
}
self.val += 1;
Some(self.val)
}
}
fn main() {
let r = RefCell::new(Foo { val: 0 });
while let Some(v) = r.borrow_mut().increment() {
println!("iteration: {}", v);
println!("borrow: {}", r.borrow().val); // panic!: BorrowError, already mutably borrowed
}
}
Mas claramente eu não uso a referência mutável r depois que ela é criada, então por que ela ainda está viva? A maneira que eu encontrei para consertar isso é:
while let Some(v) = {
let mut borrowed = r.borrow_mut();
borrowed.increment()
} {
println!("iteration: {}", v);
println!("borrow: {}", r.borrow().val); // now this works fine
}
Mas se eu tentar remover o temporário, mesmo dentro do escopo, ele ainda quebra.
while let Some(v) = { r.borrow_mut().increment() } {
println!("iteration: {}", v);
println!("borrow: {}", r.borrow().val); // panic! BorrowError
}
Além disso, esses erros me parecem ser específicos de while let
, porque não me deparei com esse erro ao verificar alguma propriedade diretamente sem while let. Então, qual é exatamente o mecanismo/regra aqui que governa como o RefCell atualiza seu contador de empréstimo?
RefCell::borrow_mut()
não retorna apenas uma referência mutável, mas umRefMut
. Esta é uma struct com um destruidor que atualiza aRefCell
contagem de empréstimos, então está sujeita às regras para quando os destruidores executam . Essas regras especificam o tempo puramente em termos de escopos — não "após o último uso" como a verificação de empréstimos permite para um&mut Foo
.No caso de
while let Some(v) = r.borrow_mut().increment()
, oRefMut
não é atribuído a nenhuma variável nomeada, o que significa que ele é armazenado em uma variável temporária que é descartada no final do escopo temporário . Infelizmente, essa página parece ter esquecido de mencionarwhile let
( consertar? ), mas o comportamento é o mesmo que paraif let
; o escopo temporário para a condição de awhile let
é o corpo inteiro . Isso foi projetado intencionalmente para que você possa usar empréstimos dos temporários em aif let
ouwhile let
. Por exemplo, este código pode compilar:Mas se o escopo temporário fosse mais restrito, o
RefMut
seria abandonado muito cedo e os empréstimos seriam impossíveis.