Cell<T> e RefCell<T>: Mutabilidade Interior

Guia completo de Cell e RefCell em Rust: mutabilidade interior, borrow, borrow_mut, runtime borrow checking e Rc<RefCell<T>> em português.

O que faz e quando usar

Cell<T> e RefCell<T> implementam o padrão de mutabilidade interior (interior mutability) em Rust. Normalmente, o sistema de borrowing do Rust é verificado em tempo de compilação: você pode ter múltiplas referências imutáveis (&T) ou uma única referência mutável (&mut T), mas nunca ambas ao mesmo tempo. Cell e RefCell permitem relaxar essa regra, movendo a verificação para tempo de execução ou contornando-a com tipos Copy.

  Verificação normal do Rust:        Mutabilidade interior:

  &T  &T  &T  (OK: múltiplas)        &T que permite mutar o interior!
  &mut T       (OK: uma só)           Cell<T>   → get/set para Copy
  &T + &mut T  (ERRO de compilação)  RefCell<T> → borrow checking em runtime

Use Cell<T> quando:

  • T implementa Copy (inteiros, bool, etc.).
  • Você precisa mutar um valor dentro de uma referência compartilhada &self.
  • Não precisa de referências ao valor interno — apenas get/set.

Use RefCell<T> quando:

  • T não implementa Copy (String, Vec, structs complexas).
  • Você precisa de referências ao valor interno (borrow/borrow_mut).
  • Sabe que o padrão de borrowing é correto, mas o compilador não consegue provar.

Importante: Cell e RefCell NÃO são thread-safe! Para multithreading, use Mutex<T> ou RwLock<T>.


Tipos e Funções Principais

Cell

ItemDescrição
Cell::new(value)Cria nova Cell contendo value
cell.get()Retorna uma cópia do valor (requer T: Copy)
cell.set(value)Substitui o valor (sem precisar de &mut self)
cell.replace(value)Substitui e retorna o valor anterior
cell.into_inner()Consome a Cell e retorna o valor
cell.take()Retorna o valor e deixa Default::default()

RefCell

ItemDescrição
RefCell::new(value)Cria nova RefCell contendo value
refcell.borrow()Retorna Ref<T> (referência imutável com runtime check)
refcell.borrow_mut()Retorna RefMut<T> (referência mutável com runtime check)
refcell.try_borrow()Tenta borrow, retorna Result<Ref<T>, BorrowError>
refcell.try_borrow_mut()Tenta borrow_mut, retorna Result<RefMut<T>, BorrowMutError>
refcell.into_inner()Consome a RefCell e retorna o valor
refcell.replace(value)Substitui o valor e retorna o anterior

Exemplos de Código

Cell — mutando valores Copy

use std::cell::Cell;

struct Contador {
    valor: Cell<u32>,
    nome: String,
}

impl Contador {
    fn new(nome: &str) -> Self {
        Contador {
            valor: Cell::new(0),
            nome: nome.to_string(),
        }
    }

    // Note: &self, NÃO &mut self!
    fn incrementar(&self) {
        let atual = self.valor.get();
        self.valor.set(atual + 1);
    }

    fn valor(&self) -> u32 {
        self.valor.get()
    }
}

fn main() {
    let contador = Contador::new("visitas");

    // Podemos mutar mesmo com referência imutável
    contador.incrementar();
    contador.incrementar();
    contador.incrementar();

    println!("{}: {}", contador.nome, contador.valor());
    // visitas: 3

    // Múltiplas referências imutáveis funcionam
    let ref1 = &contador;
    let ref2 = &contador;
    ref1.incrementar();
    ref2.incrementar();
    println!("Após referências: {}", contador.valor());
    // Após referências: 5
}

RefCell — mutando valores não-Copy

use std::cell::RefCell;

fn main() {
    let dados = RefCell::new(vec![1, 2, 3]);

    // Empréstimo imutável (borrow)
    {
        let leitura = dados.borrow();
        println!("Leitura: {:?}", *leitura);
        // Múltiplos borrows imutáveis simultâneos são OK
        let leitura2 = dados.borrow();
        println!("Leitura2: {:?}", *leitura2);
    } // Ambos os Ref são dropados aqui

    // Empréstimo mutável (borrow_mut)
    {
        let mut escrita = dados.borrow_mut();
        escrita.push(4);
        escrita.push(5);
        println!("Após push: {:?}", *escrita);
    } // RefMut dropado aqui

    println!("Final: {:?}", dados.borrow());
}

Panic em runtime: violação de borrow

use std::cell::RefCell;

fn main() {
    let dados = RefCell::new(vec![1, 2, 3]);

    let leitura = dados.borrow(); // borrow imutável ativo

    // PANIC! Não pode ter borrow_mut enquanto borrow imutável existe
    // let mut escrita = dados.borrow_mut();
    // thread 'main' panicked at 'already borrowed: BorrowMutError'

    // Versão segura com try_borrow_mut
    match dados.try_borrow_mut() {
        Ok(mut escrita) => escrita.push(4),
        Err(e) => println!("Não pode mutar: {}", e),
        // Não pode mutar: already borrowed
    }

    println!("Leitura: {:?}", *leitura);
    drop(leitura); // Agora podemos mutar

    // Agora funciona!
    dados.borrow_mut().push(4);
    println!("Após mutação: {:?}", dados.borrow());
}

Padrão: Rc<RefCell> — dados compartilhados mutáveis

Rc<RefCell<T>> é o padrão mais comum para múltiplos donos de dados mutáveis em single-thread:

  +-------+     +-------+     +-------+
  | Dono A |     | Dono B |     | Dono C |
  +---+---+     +---+---+     +---+---+
      |             |             |
      +------+------+------+------+
             |             |
          Rc::clone     Rc::clone
             |             |
        +----+-------------+----+
        |   Rc<RefCell<Vec>>    |
        |   +----------------+  |
        |   | RefCell<Vec>   |  |
        |   | +------------+ |  |
        |   | | Vec dados  | |  |
        |   | +------------+ |  |
        |   +----------------+  |
        +-----------------------+
use std::cell::RefCell;
use std::rc::Rc;

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

impl No {
    fn new(valor: i32) -> Rc<RefCell<No>> {
        Rc::new(RefCell::new(No {
            valor,
            filhos: Vec::new(),
        }))
    }

    fn adicionar_filho(pai: &Rc<RefCell<No>>, filho: Rc<RefCell<No>>) {
        pai.borrow_mut().filhos.push(filho);
    }
}

fn main() {
    let raiz = No::new(1);
    let filho_a = No::new(2);
    let filho_b = No::new(3);

    // Múltiplas referências ao mesmo nó
    No::adicionar_filho(&raiz, Rc::clone(&filho_a));
    No::adicionar_filho(&raiz, Rc::clone(&filho_b));

    // Mutar um nó filho através de qualquer referência
    filho_a.borrow_mut().valor = 20;

    // A mudança é visível em todos os lugares
    let raiz_ref = raiz.borrow();
    for filho in &raiz_ref.filhos {
        println!("Filho: {}", filho.borrow().valor);
    }
    // Filho: 20
    // Filho: 3
}

Cell para flags e caches em structs

use std::cell::Cell;

struct Fibonacci {
    cache_calculado: Cell<bool>,
    ultimo_n: Cell<u32>,
    ultimo_resultado: Cell<u64>,
}

impl Fibonacci {
    fn new() -> Self {
        Fibonacci {
            cache_calculado: Cell::new(false),
            ultimo_n: Cell::new(0),
            ultimo_resultado: Cell::new(0),
        }
    }

    // &self — sem mut!
    fn calcular(&self, n: u32) -> u64 {
        if self.cache_calculado.get() && self.ultimo_n.get() == n {
            return self.ultimo_resultado.get();
        }

        let resultado = Self::fib(n);
        self.ultimo_n.set(n);
        self.ultimo_resultado.set(resultado);
        self.cache_calculado.set(true);
        resultado
    }

    fn fib(n: u32) -> u64 {
        match n {
            0 => 0,
            1 => 1,
            _ => {
                let mut a: u64 = 0;
                let mut b: u64 = 1;
                for _ in 2..=n {
                    let tmp = a + b;
                    a = b;
                    b = tmp;
                }
                b
            }
        }
    }
}

fn main() {
    let fib = Fibonacci::new();

    // Mesmo com &fib imutável, o cache é atualizado internamente
    let ref1 = &fib;
    println!("fib(10) = {}", ref1.calcular(10)); // calcula
    println!("fib(10) = {}", ref1.calcular(10)); // usa cache
    println!("fib(20) = {}", fib.calcular(20));  // calcula novamente
}

RefCell para cache preguiçoso de String

use std::cell::RefCell;

struct Usuario {
    nome: String,
    sobrenome: String,
    nome_completo_cache: RefCell<Option<String>>,
}

impl Usuario {
    fn new(nome: &str, sobrenome: &str) -> Self {
        Usuario {
            nome: nome.to_string(),
            sobrenome: sobrenome.to_string(),
            nome_completo_cache: RefCell::new(None),
        }
    }

    // &self — sem mut, mas atualiza o cache internamente
    fn nome_completo(&self) -> String {
        let cache = self.nome_completo_cache.borrow();
        if let Some(ref cached) = *cache {
            return cached.clone();
        }
        drop(cache); // liberar borrow imutável antes de borrow_mut

        let completo = format!("{} {}", self.nome, self.sobrenome);
        *self.nome_completo_cache.borrow_mut() = Some(completo.clone());
        completo
    }
}

fn main() {
    let user = Usuario::new("Maria", "Silva");

    println!("{}", user.nome_completo()); // calcula e faz cache
    println!("{}", user.nome_completo()); // usa cache
}

Padrões Comuns e Anti-padrões

Quando usar Cell vs RefCell

use std::cell::{Cell, RefCell};

fn main() {
    // Cell: para tipos Copy — mais eficiente, sem overhead de contagem
    let flag = Cell::new(false);
    let contador = Cell::new(0u32);
    flag.set(true);
    contador.set(contador.get() + 1);

    // RefCell: para tipos não-Copy — quando precisa de referências
    let texto = RefCell::new(String::from("olá"));
    texto.borrow_mut().push_str(" mundo");
    println!("{}", texto.borrow());

    // ERRADO: usar RefCell para tipos Copy (overhead desnecessário)
    // let valor = RefCell::new(42);

    // CORRETO: usar Cell para tipos Copy
    let valor = Cell::new(42);
    valor.set(valor.get() + 1);
}

Anti-padrão: borrow_mut de longa duração

use std::cell::RefCell;

fn main() {
    let dados = RefCell::new(vec![1, 2, 3]);

    // ERRADO: manter borrow_mut por muito tempo
    // let mut guard = dados.borrow_mut();
    // // ... muito código ...
    // let leitura = dados.borrow(); // PANIC!

    // CORRETO: escopo curto para borrow_mut
    {
        dados.borrow_mut().push(4);
    }
    // Agora borrow() é seguro
    println!("{:?}", dados.borrow());
}

Anti-padrão: RefCell entre threads

use std::cell::RefCell;

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

    // ERRO DE COMPILAÇÃO: RefCell não é Sync
    // std::thread::spawn(move || {
    //     *dados.borrow_mut() += 1;
    // });

    // Para threads, use Mutex<T> em vez de RefCell<T>
    // Para múltiplos donos entre threads, use Arc<Mutex<T>> em vez de Rc<RefCell<T>>
    println!("Valor: {}", dados.borrow());
}

Garantias de Thread Safety

  • Cell<T> e RefCell<T> NÃO implementam Syncnão são thread-safe.
  • Cell<T> é Send se T: Send.
  • RefCell<T> é Send se T: Send.
  • Nenhum dos dois pode ser compartilhado entre threads com & (porque não são Sync).
  • Para o equivalente thread-safe: use Mutex<T> no lugar de RefCell<T> e AtomicT no lugar de Cell<T>.
  • RefCell mantém um contador de borrows em runtime: panics ocorrem se as regras de borrow forem violadas.
  • Cell não tem overhead de contagem — é zero-cost para tipos Copy.
Single-threadMulti-thread
Cell<T>AtomicT / Mutex<T>
RefCell<T>RwLock<T> / Mutex<T>
Rc<RefCell<T>>Arc<Mutex<T>> / Arc<RwLock<T>>

Veja Também