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:
TimplementaCopy(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:
Tnão implementaCopy(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
| Item | Descriçã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
| Item | Descriçã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>eRefCell<T>NÃO implementamSync— não são thread-safe.Cell<T>éSendseT: Send.RefCell<T>éSendseT: Send.- Nenhum dos dois pode ser compartilhado entre threads com
&(porque não sãoSync). - Para o equivalente thread-safe: use
Mutex<T>no lugar deRefCell<T>eAtomicTno lugar deCell<T>. RefCellmantém um contador de borrows em runtime: panics ocorrem se as regras de borrow forem violadas.Cellnão tem overhead de contagem — é zero-cost para tiposCopy.
| Single-thread | Multi-thread |
|---|---|
Cell<T> | AtomicT / Mutex<T> |
RefCell<T> | RwLock<T> / Mutex<T> |
Rc<RefCell<T>> | Arc<Mutex<T>> / Arc<RwLock<T>> |
Veja Também
- Rc e Arc em Rust — ponteiros de contagem de referência
- Mutex e RwLock em Rust — equivalentes thread-safe
- Send e Sync — por que Cell/RefCell não são Sync
- Tipos Atômicos — equivalentes atômicos de Cell
- Smart Pointers em Rust — Box, Rc, Arc, Cell, RefCell
- Documentação oficial:
std::cell