O que é Interior Mutability?
Interior mutability é um padrão de design em Rust que permite modificar dados através de uma referência imutável. Normalmente, Rust impõe que você tenha ou uma referência mutável exclusiva (&mut T) ou várias referências imutáveis (&T), nunca ambas simultaneamente. Interior mutability quebra essa regra em tempo de execução, permitindo mutações controladas sem violar a segurança de memória. Isso é essencial quando você precisa compartilhar propriedade sobre dados e ainda assim modificá-los — um cenário comum em estruturas como gráficos, caches ou callbacks.
O custo dessa flexibilidade é a verificação em tempo de execução. Enquanto o borrow checker valida referências em compile-time, interior mutability valida em runtime, podendo causar panic se as regras forem violadas. RefCell<T> é a ferramenta mais prática para isso: fornece mutabilidade interior com verificação dinâmica de borrowing.
RefCell: Estrutura e Funcionamento
RefCell<T> é um wrapper que armazena um valor T e mantém controle sobre quantas referências imutáveis (Ref<T>) e mutáveis (RefMut<T>) existem em tempo de execução. Diferentemente de Box<T> que apenas aloca memória, RefCell<T> adiciona metadados para rastrear empréstimos.
use std::cell::RefCell;
#[derive(Debug)]
struct Usuario {
nome: String,
saldo: RefCell<i32>,
}
impl Usuario {
fn novo(nome: String, saldo: i32) -> Self {
Usuario {
nome,
saldo: RefCell::new(saldo),
}
}
fn depositar(&self, valor: i32) {
// &self é imutável, mas podemos mutar saldo através de RefCell
let mut saldo = self.saldo.borrow_mut();
*saldo += valor;
}
fn consultar_saldo(&self) -> i32 {
*self.saldo.borrow()
}
}
fn main() {
let usuario = Usuario::novo("Alice".to_string(), 100);
usuario.depositar(50);
println!("Saldo: {}", usuario.consultar_saldo()); // Saldo: 150
}
Dois métodos são centrais: borrow() retorna Ref<T> (referência imutável temporária) e borrow_mut() retorna RefMut<T> (referência mutável temporária). Estas são liberadas quando saem de escopo. Se você tentar fazer borrow_mut() enquanto um borrow() ativo existe, o programa faz panic — justamente a validação em tempo de execução.
Casos de Uso Práticos
Cache e Lazy Evaluation
Um uso comum é implementar valores calculados sob demanda:
use std::cell::RefCell;
struct Computacao {
entrada: i32,
resultado: RefCell<Option<i32>>,
}
impl Computacao {
fn novo(entrada: i32) -> Self {
Computacao {
entrada,
resultado: RefCell::new(None),
}
}
fn calcular(&self) -> i32 {
let mut res = self.resultado.borrow_mut();
if res.is_none() {
println!("Calculando...");
*res = Some(self.entrada * 2);
}
res.unwrap()
}
}
fn main() {
let comp = Computacao::novo(10);
println!("{}", comp.calcular()); // Calcula: 20
println!("{}", comp.calcular()); // Usa cache: 20
}
Observadores e Callbacks
Estruturas que precisam notificar múltiplos observadores muitas vezes usam RefCell:
use std::cell::RefCell;
struct Modelo {
dados: RefCell<String>,
observadores: RefCell<Vec<Box<dyn Fn(&str)>>>,
}
impl Modelo {
fn novo() -> Self {
Modelo {
dados: RefCell::new(String::new()),
observadores: RefCell::new(Vec::new()),
}
}
fn registrar_observador(&self, callback: Box<dyn Fn(&str)>) {
self.observadores.borrow_mut().push(callback);
}
fn atualizar(&self, novo_valor: String) {
*self.dados.borrow_mut() = novo_valor.clone();
for observador in self.observadores.borrow().iter() {
observador(&novo_valor);
}
}
}
fn main() {
let modelo = Modelo::novo();
modelo.registrar_observador(Box::new(|v| println!("Notificado: {}", v)));
modelo.atualizar("Nova dados".to_string());
}
Alternativas e Quando Usar
RefCell<T> não é a única solução. Cell<T> é semelhante mas não oferece referências — apenas cópia/substituição completa de valores, útil para tipos pequenos como inteiros. Mutex<T> é thread-safe e apropriado para ambientes concorrentes. Arc<T> combina contagem de referências com Mutex<T> para compartilhamento seguro entre threads.
Escolha RefCell<T> quando: (1) você está em single-thread, (2) precisa mutar campos dentro de um método que recebe &self, e (3) está confiante que seus empréstimos em tempo de execução serão válidos. Evite quando há risco de panics frequentes — eles indicam design problemático. Se frequentemente você tenta fazer múltiplos borrow_mut() simultâneos, reconsidere a arquitetura.
// ❌ Evite
let cell = RefCell::new(5);
let _r1 = cell.borrow_mut();
let _r2 = cell.borrow_mut(); // PANIC!
// ✅ Melhor
let cell = RefCell::new(5);
{
let _r1 = cell.borrow_mut();
} // r1 liberado aqui
let _r2 = cell.borrow_mut(); // OK
Conclusão
RefCell<T> e interior mutability são ferramentas poderosas que desbloqueiam padrões de design impossíveis com as regras normais de borrowing. Eles permitem mutações através de referências imutáveis, pagando o preço com verificações em runtime. Três pontos-chave: primeiro, use RefCell<T> para contornar limitações do borrow checker em código single-thread, mantendo segurança de memória; segundo, sempre estruture seu código para evitar múltiplos empréstimos mutáveis simultâneos — panics indicam bugs; terceiro, considere se sua arquitetura realmente exige interior mutability ou se uma refatoração resolveria melhor.