RefCell e interior mutability

1. O Padrão Interior Mutability em Rust

O padrão interior mutability (mutabilidade interior) é uma das características mais poderosas e controversas do sistema de tipos de Rust. Ele permite que você mute dados através de referências imutáveis, contornando as regras de borrowing que o compilador normalmente impõe em tempo de compilação.

A motivação é simples: existem situações onde você precisa de mutabilidade, mas as regras do borrow checker impedem. Por exemplo, quando você tem um valor compartilhado por múltiplas referências imutáveis (via Rc<T>) e precisa modificá-lo.

Em Rust, a mutabilidade é verificada em tempo de compilação (compile-time). O padrão interior mutability move essa verificação para tempo de execução (runtime), trocando segurança estática por flexibilidade dinâmica.

Cenários reais onde isso é necessário:
- Implementação de caches que precisam ser atualizados mesmo com referências imutáveis
- Mocks em testes que precisam registrar chamadas
- Estruturas de dados como grafos com referências compartilhadas

2. Entendendo o RefCell

RefCell<T> é um tipo de smart pointer que implementa o padrão interior mutability. Diferente de Box<T>, que segue as regras de borrowing estritamente em tempo de compilação, RefCell<T> as aplica em tempo de execução.

use std::cell::RefCell;

fn main() {
    let valor = RefCell::new(42);

    // Borrow imutável em tempo de execução
    let ref1 = valor.borrow();
    let ref2 = valor.borrow(); // Múltiplos borrows imutáveis são permitidos
    println!("{} {}", ref1, ref2);

    // Borrow mutável
    *valor.borrow_mut() += 1;
    println!("{}", valor.borrow());
}

A diferença fundamental:
- Box<T>: ownership único, mutabilidade verificada em compile-time
- Rc<T>: múltiplos owners, mas apenas leitura compartilhada
- RefCell<T>: mutabilidade interior, verificada em runtime

3. Métodos Principais do RefCell

Os métodos mais importantes são borrow() e borrow_mut():

use std::cell::RefCell;

let cell = RefCell::new(String::from("Hello"));

// borrow() retorna Ref<T>, uma referência imutável
let ref_imutavel = cell.borrow();
println!("{}", ref_imutavel);

// borrow_mut() retorna RefMut<T>, uma referência mutável
let mut ref_mutavel = cell.borrow_mut();
ref_mutavel.push_str(", World!");
println!("{}", ref_mutavel);

As versões try_borrow() e try_borrow_mut() retornam Result:

let cell = RefCell::new(10);

let ref1 = cell.borrow();
let resultado = cell.try_borrow_mut(); // Retorna Err porque já há um borrow ativo
match resultado {
    Ok(_) => println!("Conseguiu borrow mutável"),
    Err(e) => println!("Erro: {}", e),
}

Métodos auxiliares úteis:

let mut cell = RefCell::new(5);

// into_inner() consome o RefCell e retorna o valor interno
let valor = cell.into_inner();
println!("{}", valor); // 5

// get_mut() retorna &mut T, mas requer &mut self
let mut cell = RefCell::new(10);
*cell.get_mut() = 20;

// replace() substitui o valor e retorna o antigo
let cell = RefCell::new(100);
let antigo = cell.replace(200);
println!("Antigo: {}, Novo: {}", antigo, cell.borrow());

4. Combinando RefCell com Rc

A combinação Rc<RefCell<T>> é extremamente poderosa: permite múltiplos owners com mutabilidade compartilhada.

use std::rc::Rc;
use std::cell::RefCell;

#[derive(Debug)]
struct No {
    valor: i32,
    vizinhos: Vec<Rc<RefCell<No>>>,
}

fn main() {
    let no_a = Rc::new(RefCell::new(No {
        valor: 1,
        vizinhos: vec![],
    }));

    let no_b = Rc::new(RefCell::new(No {
        valor: 2,
        vizinhos: vec![Rc::clone(&no_a)],
    }));

    // Adiciona referência circular
    no_a.borrow_mut().vizinhos.push(Rc::clone(&no_b));

    // Modifica através de qualquer referência
    no_b.borrow_mut().valor = 42;
    println!("{:?}", no_a.borrow());
}

Cuidado com ciclos de referência: referências circulares com Rc podem causar vazamentos de memória. Use Weak<T> para quebrar ciclos.

5. Borrow Checker em Tempo de Execução

O RefCell<T> mantém um contador interno de referências ativas:
- Múltiplos borrow() são permitidos simultaneamente
- Apenas um borrow_mut() por vez
- borrow() e borrow_mut() não podem coexistir

Violar essas regras causa panic em tempo de execução:

let cell = RefCell::new(0);

let ref_mut = cell.borrow_mut();
let ref_imut = cell.borrow(); // PANIC! Já existe um borrow mutável ativo
// thread 'main' panicked at 'already borrowed: BorrowMutError'

Boas práticas para evitar panics:
- Mantenha escopos pequenos para borrows
- Use try_borrow() quando não tiver certeza
- Documente quando borrows podem conflitar
- Considere usar Cell<T> para tipos Copy

6. Alternativas e Casos de Uso

Cell vs RefCell:
- Cell<T>: para tipos Copy, sem referências, mais eficiente
- RefCell<T>: para tipos não-Copy, permite referências

use std::cell::Cell;

let cell = Cell::new(42);
cell.set(100); // Não precisa de borrow mutável
println!("{}", cell.get()); // 100

Para concorrência: use Mutex<T> ou RwLock<T> em vez de RefCell<T>:

use std::sync::Mutex;

let mutex = Mutex::new(0);
let mut data = mutex.lock().unwrap();
*data += 1;

Em testes e mocks: RefCell é ideal para criar mocks que registram chamadas:

struct MockDatabase {
    chamadas: RefCell<Vec<String>>,
}

impl MockDatabase {
    fn executar(&self, query: &str) {
        self.chamadas.borrow_mut().push(query.to_string());
    }

    fn quantas_chamadas(&self) -> usize {
        self.chamadas.borrow().len()
    }
}

7. Padrões Avançados com RefCell

Cache mutável com RefCell:

struct Cache<T> {
    dados: RefCell<HashMap<String, T>>,
}

impl<T: Clone> Cache<T> {
    fn get_or_insert(&self, chave: &str, f: impl FnOnce() -> T) -> T {
        if let Some(valor) = self.dados.borrow().get(chave) {
            return valor.clone();
        }
        let novo_valor = f();
        self.dados.borrow_mut().insert(chave.to_string(), novo_valor.clone());
        novo_valor
    }
}

RefCell com closures mutáveis:

let contador = Rc::new(RefCell::new(0));
let closure = {
    let contador = Rc::clone(&contador);
    move || {
        *contador.borrow_mut() += 1;
        *contador.borrow()
    }
};

println!("{}", closure()); // 1
println!("{}", closure()); // 2

Estruturas auto-referenciadas: Cuidado! RefCell não resolve todos os problemas de auto-referência. Use unsafe ou bibliotecas como ouroboros para casos complexos.

Conclusão

RefCell<T> é uma ferramenta poderosa que oferece mutabilidade interior em Rust, movendo a verificação de borrowing do tempo de compilação para o tempo de execução. Use com sabedoria: ele resolve problemas reais de design, mas introduz a possibilidade de panics em runtime. Combine com Rc<T> para criar estruturas de dados compartilhadas e mutáveis, mas sempre considere alternativas mais seguras como Cell<T> para tipos simples ou Mutex<T> para concorrência.

Referências