Rust: Ponteiros inteligentes em Rust
Última atualização: 2026-08-26
Um ponteiro inteligente é uma “estrutura de dados com comportamento adicional” — ele faz referência à memória da mesma forma que um ponteiro, mas oferece recursos que vão além dos de um ponteiro comum, como gerenciamento automático de memória, contagem de referências e verificação de empréstimo.
Se um ponteiro comum é como “uma folha de papel com um endereço escrito nela”, então um ponteiro inteligente é como “um sistema de gestão imobiliária que conta com seus próprios seguranças, zeladores e livro-razão”. Os ponteiros inteligentes do Rust garantem segurança em tempo de compilação e não apresentam sobrecarga em tempo de execução (além da sobrecarga mínima da contagem de referências).
1. O que você vai aprender
Box<T>Alocação na pilha e transferência de propriedade- Característica
Deref— Sobrecarga do operador de desreferência - Característica
Drop— Limpeza automática por meio de destrutores Rc<T>Contagem de referências — Compartilhamento entre múltiplos proprietários em um único threadRefCell<T>Variabilidade interna — Verificações de empréstimo em tempo de execuçãoRc<RefCell<T>>Modo de combinação — Compartilhado + Editável
2. A história das mesas compartilhadas
(1) Problema: Vários usuários desejam visualizar e editar o mesmo documento ao mesmo tempo
Na manhã de segunda-feira, a empresa da Luna adotou uma política de “mesas compartilhadas”. Havia apenas três mesas para uma equipe de cinco pessoas.
- Alice precisa dar uma olhada em uma proposta de projeto (PDF), mas não quer imprimir uma cópia por conta própria — ela quer ver a que outra pessoa já tem.
- O Bob também precisa analisar a mesma proposta e fazer anotações sobre ela.
- Carol quer ver o documento ao mesmo tempo, mas apenas para ler — não para editá-lo.
- O problema é o seguinte: se Alice terminar de ler o documento e o destruir, mas Bob e Carol ainda estiverem lendo — é aí que as coisas dão errado.
- Para piorar a situação, enquanto Bob fazia alterações, Alice também estava fazendo alterações — e, por isso, a página ficou toda bagunçada.
“Se ao menos soubéssemos quantas pessoas ainda estão acessando este documento, poderíamos simplesmente destruí-lo assim que ninguém mais o estivesse acessando...” “Se ao menos pudéssemos garantir que, quando uma pessoa estiver editando-o, ninguém mais possa fazer alterações...”
(2) Soluções com ponteiros inteligentes em Rust
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
// Box: One person has exclusive access to a document,Destroy after reading
let doc_box = Box::new(String::from("Project Proposal v1"));
println!("Box Hold: {}", doc_box);
// doc_box Automatically destroyed upon leaving the scope——No manual intervention required free
// Rc: Shared Read-Only Access for Multiple Users
let doc_rc = Rc::new(String::from("Shared Project Proposal"));
let alice = Rc::clone(&doc_rc);
let bob = Rc::clone(&doc_rc);
println!("Reference Count: {}", Rc::strong_count(&doc_rc)); // 3
// Rc<RefCell<T>>: Share + Variable
let doc_shared = Rc::new(RefCell::new(String::from("Collaborative Documents")));
let alice_view = Rc::clone(&doc_shared);
let bob_view = Rc::clone(&doc_shared);
// Bob Add annotations to a document(Needs to be revised)
bob_view.borrow_mut().push_str("\nBob Comments on:Part 2 needs to be completed");
// Alice View the documentation(Read-only)
println!("Alice See: {}", alice_view.borrow());
// Carol See the documentation as well(Read-only)
println!("Carol See: {}", doc_shared.borrow());
}
O Rust utiliza três tipos de ponteiros inteligentes para lidar com questões de segurança entre threads relacionadas ao “compartilhamento” e à “modificação”:
Boxpropriedade exclusiva e alocação na pilha,Rcpropriedade compartilhada somente para leitura eRefCellmutabilidade interna obtida por meio de verificações em tempo de execução das regras de empréstimo.
3. Conceitos fundamentais
(1) Sistema de Ponteiros Inteligentes
graph TB
A[Rust Smart Pointers] --> B[Box<T> Heap Allocation]
A --> C[Rc<T> Reference Count]
A --> D[RefCell<T> Internal Variability]
B --> B1["let b = Box::new(42)"]
B --> B2["Automatically leaves scope drop"]
C --> C1["Rc::clone Increase the reference count"]
C --> C2["strong_count == 0 Destroy on time"]
C --> C3["Use in a single-threaded environment"]
D --> D1["borrow() → Immutable References"]
D --> D2["borrow_mut() → Variable References"]
D --> D3["Runtime Check of Borrowing Rules"]
A --> E[Combination Mode]
E --> E1["Rc<RefCell<T>>"]
E --> E2["Shared Ownership + Internal Variability"]
A --> F[Core Trait]
F --> F1["Deref: Dereference Operator *"]
F --> F2["Drop: Destructor"]
(2) Comparação entre três tipos de agulhas inteligentes
| Propriedade | Box<T> | Rc<T> | RefCell<T> |
|---|---|---|---|
| Propriedade | Propriedade única | Propriedade compartilhada (contagem de referências) | Propriedade única |
| Mutabilidade | Mutável (Box::new seguido por *b = val) |
Imutável (compartilhado somente para leitura) | Mutável internamente (verificado em tempo de execução) |
| Verificação de tempo | Tempo de compilação | Tempo de compilação | Tempo de execução |
| Sobrecarga de desempenho | Sem sobrecarga adicional | Alterações na contagem de referências (mínimas) | Verificações de empréstimo em tempo de execução (mínimas) |
| Seguro para múltiplas threads | Sim | Não (mono-thread) | Não (mono-thread) |
| Casos de uso | Conjuntos de dados grandes alocados na pilha, tipos recursivos | Dados compartilhados de leitura exclusiva com múltiplos caminhos | Necessidade de modificar dados sob referências imutáveis |
(3) Traços “Deref” e “Drop”
| Característica | Método | Função |
|---|---|---|
Deref |
fn deref(&self) -> &T |
Permitir que as operações de desreferenciamento *x sejam aplicadas a tipos personalizados |
Drop |
fn drop(&mut self) |
Lógica de limpeza chamada automaticamente quando um valor sai de seu escopo |
(4) Comparação entre os modelos Box, Rc e Arc
| Características | Box<T> |
Rc<T> |
Arc<T> |
|---|---|---|---|
| Propriedade | Proprietário único | Vários proprietários (contagem de referências) | Vários proprietários (contagem atômica de referências) |
| Seguro para threads | Não (apenas para uma única thread) | Não (apenas para uma única thread) | Sim (pode ser compartilhado entre threads) |
| Contagem de referências | Nenhuma | Não atômica, baixa sobrecarga | Operação atômica, sobrecarga ligeiramente maior |
| Acesso à variável | &mut Variável diretamente |
Requer RefCell |
Requer Mutex / RwLock |
| Cenários típicos | Tipos recursivos / Alocação em heap para Big Data | DAG / Dados compartilhados somente para leitura | Compartilhamento de dados em multithread |
| Desempenho | Mais rápido | Média | Um pouco mais lento (operações atômicas) |
DerefFaz com que os ponteiros inteligentes funcionem exatamente como referências comuns —Box<T>pode ser usado da mesma forma que&T.Dropgarante que os recursos sejam liberados automaticamente — não há necessidade defreeoudeletemanualmente.
4. Exemplos de ponteiros inteligentes
▶ Exemplo 1: Box — Alocação em heap e tipos recursivos (Dificuldade ⭐)
// ============================================
// Box<T> Three Typical Uses of:Heap Allocation、Recursive Types、Deref
// ============================================
// Recursive Types:Cons list(Must use Box,Because the size is unknown at compile time)
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
// Custom Smart Pointers (Demo Deref and Drop)
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0 // A reference to the internal value
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
// In practical applications, resources are released here(For example, closing a file、Release the network connection)
// println!("MyBox It was destroyed.~");
}
}
fn main() {
// --- 1. Box Basic Heap Allocation ---
let b = Box::new(42);
println!("Box the value in: {}", b); // Automatic Dereferencing
// --- 2. Recursive Types:Cons List ---
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("Recursive List: {:?}", list);
// --- 3. Custom MyBox + Deref ---
let my_box = MyBox::new(String::from("Hello"));
// Deref lets &MyBox<String> automatically convert to &String, then to &str
greet(&my_box);
// --- 4. Dereference Operator ---
let x = 10;
let y = MyBox::new(x);
assert_eq!(10, *y); // *y Equivalent to *(y.deref())
println!("*y == {}", *y);
}
fn greet(name: &str) {
println!("Greeting: {}", name);
}
Resultado:
Box the value in: 42
Recursive List: Cons(1, Cons(2, Cons(3, Nil)))
Greeting: Hello
*y == 10
O valor central do
Box<T>é duplo: primeiro, ele aloca os dados na pilha (retendo apenas ponteiros na pilha); segundo, permite que tipos cujo tamanho é desconhecido em tempo de compilação (como tipos recursivos) sejam usados normalmente. A característicaDerefpermite queBox<T>seja desreferenciado exatamente como&T— é daí que vem o termo “inteligente” em “ponteiro inteligente”.
▶ Exemplo 2: Rc — Contagem de referências com propriedade compartilhada (Dificuldade ⭐⭐)
// ============================================
// Rc<T> Reference Count:Multipath Sharing of Read-Only Data
// ============================================
use std::rc::Rc;
#[derive(Debug)]
enum BookList {
Cons(String, Rc<BookList>),
Nil,
}
use BookList::{Cons, Nil};
fn main() {
// --- Scene:Three books are shared by two readers ---
// Create a Basic Reading List
let book_c = Rc::new(Cons("Rust Programming".to_string(),
Rc::new(Cons("Introduction to Algorithms".to_string(),
Rc::new(Cons("Design Patterns".to_string(),
Rc::new(Nil))))));
println!("Initial reference count: {}", Rc::strong_count(&book_c));
// Alice Borrowed the reading list(Rc::clone Increase the reference count only,Do not deep-copy data)
let alice = Rc::clone(&book_c);
println!("Alice Reference Count After Borrowing: {}", Rc::strong_count(&book_c));
{
// Bob I also borrowed the reading list(In the inner scope)
let bob = Rc::clone(&book_c);
println!("Bob Reference Count After Borrowing: {}", Rc::strong_count(&book_c));
// Both of them can read it.
println!("Alice Book List: {:?}", *alice);
println!("Bob Book List: {:?}", *bob);
} // Bob Out of scope,Decrease in reference count
println!("Bob Citation Count After Returning the Book: {}", Rc::strong_count(&book_c));
// Alice Still reading
println!("Alice Still reading: {:?}", *alice);
// All Rc After they have all gone out of scope,Only then is the data truly destroyed
// Rc::strong_count == 0 When triggered drop
}
Resultado:
Initial reference count: 1
Alice Reference Count After Borrowing: 2
Bob Reference Count After Borrowing: 3
Alice Book List: Cons("Rust Programming", Cons("Introduction to Algorithms", Cons("Design Patterns", Nil)))
Bob Book List: Cons("Rust Programming", Cons("Introduction to Algorithms", Cons("Design Patterns", Nil)))
Bob Citation Count After Returning the Book: 2
Alice Still reading: Cons("Rust Programming", Cons("Introduction to Algorithms", Cons("Design Patterns", Nil)))
Rc<T>significa “Contagem de Referências”.Rc::clone(&x)não realiza uma cópia profunda dos dados; simplesmente incrementa a contagem de referências em 1. Os dados só são destruídos quando todos os identificadoresRcsaem do escopo (e a contagem de referências chega a zero).Rcsó pode ser usado em um ambiente de thread único — para multithreading, useArc<T>.
▶ Exemplo 3: RefCell — Variabilidade interna (Dificuldade ⭐⭐)
// ============================================
// RefCell<T>:Runtime Borrowing Checks + Internal Variability
// ============================================
use std::cell::RefCell;
// Simulate a "Log Entry" Messenger
// External code can only obtain immutable references, but the log needs to be modified internally.
trait Messenger {
fn send(&self, msg: &str);
}
// Logger:For internal use only RefCell Store Messages
struct Logger {
// Even if Logger Inherently immutable,messages It can still be edited
messages: RefCell<Vec<String>>,
}
impl Logger {
fn new() -> Logger {
Logger {
messages: RefCell::new(Vec::new()),
}
}
}
impl Messenger for Logger {
fn send(&self, msg: &str) {
// borrow_mut() gets a mutable reference — even if self is &self
self.messages.borrow_mut().push(msg.to_string());
}
}
fn main() {
let logger = Logger::new();
// Calling via an immutable reference send——But it has actually been modified internally.
logger.send("User Login");
logger.send("Click the button");
logger.send("Data submitted successfully");
// borrow() Get an immutable reference
let msgs = logger.messages.borrow();
for (i, msg) in msgs.iter().enumerate() {
println!("Log #{}: {}", i + 1, msg);
}
// --- Examples of Runtime Borrowing Violations(Cancel the Commentary Session panic)---
// let mut_ref = logger.messages.borrow_mut();
// let ref1 = logger.messages.borrow(); // panic! Both mutable and immutable references
// println!("{}", ref1[0]);
println!("RefCell The presentation is over.");
}
Resultado:
Log #1: User Login
Log #2: Click the button
Log #3: Data submitted successfully
RefCell The presentation is over.
O princípio central do
RefCell<T>é a “mutabilidade interna”: mesmo que o próprioLoggerseja uma referência imutável ao&self, oRefCellainda permite que você modifique os dados internos. A diferença é que as regras de empréstimo não são verificadas em tempo de compilação, mas sim em tempo de execução — se uma regra for violada, o programa entrará em pânico em vez de gerar um erro em tempo de compilação.
▶ Exemplo 4: Rc<RefCell<T>> — Compartilhado + Mutável (Dificuldade ⭐⭐⭐)
// ============================================
// Rc<RefCell<T>> Combination Mode:Shared Ownership + Internal Variability
// ============================================
use std::rc::Rc;
use std::cell::RefCell;
// Simulate a"Shared Whiteboard"——Team members can write and read
#[derive(Debug)]
struct Whiteboard {
content: RefCell<String>,
}
impl Whiteboard {
fn new() -> Whiteboard {
Whiteboard {
content: RefCell::new(String::new()),
}
}
fn write(&self, text: &str) {
let mut content = self.content.borrow_mut();
content.push_str(text);
content.push('\n');
}
fn read(&self) -> String {
self.content.borrow().clone()
}
}
fn main() {
// Create a shared whiteboard, wrapped with Rc so it can be held by multiple people
let board = Rc::new(Whiteboard::new());
// Alice and Bob both hold references to the whiteboard
let alice_board = Rc::clone(&board);
let bob_board = Rc::clone(&board);
let carol_board = Rc::clone(&board);
println!("Current reference count: {}", Rc::strong_count(&board)); // 4
// Alice Write on the whiteboard
alice_board.write("Alice: Today's Discussion Rust Smart Pointers");
// Bob Additional Information
bob_board.write("Bob: I'll explain the difference between Box and Rc");
// Carol Write as well
carol_board.write("Carol: RefCell Runtime borrowing checks are important");
// Everyone can view the full content(Because they share the same RefCell)
println!("=== Whiteboard Content ===");
println!("{}", board.read());
// Verify that all references point to the same data
println!("Alice Length of content viewed: {}", alice_board.read().len());
println!("Bob Length of content viewed: {}", bob_board.read().len());
println!("Carol Length of content viewed: {}", carol_board.read().len());
// board When leaving the scope,Reference Count Reset to Zero,Whiteboard Automatic Destruction
}
Resultado:
Current reference count: 4
=== Whiteboard Content ===
Alice: Today's Discussion Rust Smart Pointers
Bob: I'll explain the difference between Box and Rc
Carol: RefCell Runtime borrowing checks are important
Alice Length of content viewed: 73
Bob Length of content viewed: 73
Carol Length of content viewed: 73
Rc<RefCell<T>>é um dos padrões composíveis mais poderosos na programação em Rust com um único thread:Rcresolve o problema de “várias pessoas quererem manter” algo, enquantoRefCellresolve o problema de “pessoas que mantêm referências imutáveis também quererem modificá-lo”. Se compararmosRca um “cartão de biblioteca compartilhado”, entãoRefCellé uma “cópia especial que permite anotações”.
▶ Exemplo 5: Exercício abrangente — Estruturas de dados em grafos (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example:Rc<RefCell<T>> Graph Node Implementation
// ============================================
use std::rc::{Rc, Weak};
use std::cell::RefCell;
use std::fmt;
struct Node {
value: String,
neighbors: Vec<Weak<RefCell<Node>>>,
}
impl Node {
fn new(value: &str) -> Rc<RefCell<Node>> {
Rc::new(RefCell::new(Node {
value: value.to_string(),
neighbors: Vec::new(),
}))
}
fn add_neighbor(node: &Rc<RefCell<Node>>, neighbor: &Rc<RefCell<Node>>) {
node.borrow_mut().neighbors.push(Rc::downgrade(neighbor));
}
}
impl fmt::Debug for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let neighbor_names: Vec<String> = self.neighbors.iter()
.filter_map(|w| w.upgrade())
.map(|n| n.borrow().value.clone())
.collect();
write!(f, "{} -> {:?}", self.value, neighbor_names)
}
}
fn main() {
let a = Node::new("A");
let b = Node::new("B");
let c = Node::new("C");
let d = Node::new("D");
Node::add_neighbor(&a, &b);
Node::add_neighbor(&a, &c);
Node::add_neighbor(&b, &c);
Node::add_neighbor(&b, &d);
Node::add_neighbor(&c, &d);
println!("=== Graph Structure ===");
for node in [&a, &b, &c, &d] {
println!("{:?}", node.borrow());
}
println!("\n=== Nodes Reachable from Starting Point A ===");
if let Some(neighbor) = a.borrow().neighbors.first().and_then(|w| w.upgrade()) {
println!("A's first neighbor: {}", neighbor.borrow().value);
let second_hop: Vec<String> = neighbor.borrow().neighbors.iter()
.filter_map(|w| w.upgrade())
.map(|n| n.borrow().value.clone())
.collect();
println!("Take it a step further from that neighbor: {:?}", second_hop);
}
println!("\n=== Strong reference counting ===");
println!("A's Rc reference count: {}", Rc::strong_count(&a));
let a_clone = Rc::clone(&a);
println!("After clone, A's Rc reference count: {}", Rc::strong_count(&a));
drop(a_clone);
println!("After drop, A's Rc reference count: {}", Rc::strong_count(&a));
}
Resultado:
=== Graph Structure ===
A -> ["B", "C"]
B -> ["C", "D"]
C -> ["D"]
D -> []
=== Nodes Reachable from Starting Point A ===
A's first neighbor: B
Take it a step further from that neighbor: ["C", "D"]
=== Strong reference counting ===
A's Rc reference count: 1
After clone, A's Rc reference count: 2
After drop, A's Rc reference count: 1
Os nós do gráfico utilizam
Rc<RefCell<Node>>para implementar a propriedade compartilhada e a mutabilidade interna; as relações de vizinhança utilizamWeak<RefCell<Node>>para evitar vazamentos de memória causados por referências circulares.Rc::downgradecria uma referência fraca, eweak.upgrade()tenta transformá-la em uma referência forte.
❓ Perguntas Frequentes
P: Qual é a diferença entre
Box<T>e uma referência comum&T? R:Box<T>é proprietária dos dados, enquanto&Tapenas os toma emprestado.Box<T>descarta os dados (libera memória do heap) quando sai do escopo, enquanto&Tapenas os toma emprestados e não afeta o ciclo de vida dos dados.Box<T>garante que os dados residam no heap, e&Tpode apontar tanto para a pilha quanto para o heap.
P: Qual é a diferença entre
Rc<T>eArc<T>? R:Rc<T>é uma contagem de referências em um único thread, enquantoArc<T>é uma contagem de referências atômica em múltiplos threads.Rcutiliza operações de contagem não atômicas (para melhor desempenho) e não pode ser usado entre threads.Arcutiliza operações atômicas (com uma ligeira sobrecarga de desempenho) e pode ser usado entre threads. Os requisitos da característicaSenddeterminam queRcnão pode ser usado entre threads.
P: Qual é a diferença entre
RefCell<T>eCell<T>? R:Cell<T>implementa a mutabilidade interna por meio da cópia (ou movimentação) de valores, enquantoRefCell<T>a implementa por meio de referências. Os métodosget/setdeCell<T>requeremT: Copye são adequados para tipos pequenos.RefCell<T>pode ser usado com qualquer tipo; requer a obtenção de uma referência por meio deborrow/borrow_mut, e as regras de empréstimo são verificadas em tempo de execução.
P: Quando se deve usar
RefCell<T>em vez de&mut T? R: UseRefCellquando você tiver uma referência imutável, mas precisar modificar os dados. Cenário comum: a assinatura de um método de trait aceita apenas&self, mas o método precisa modificar o estado internamente (por exemplo, caches, logs, contadores).RefCelladia a verificação de empréstimo do tempo de compilação para o tempo de execução.
P: Qual é a diferença de desempenho entre
Rc<RefCell<T>>e&mut T? R:Rc<RefCell<T>>acarreta sobrecarga devido à contagem de referências e às verificações de empréstimo em tempo de execução, enquanto&mut Tnão apresenta sobrecarga. Cada chamada aoRc::cloneadiciona uma operação atômica (não atômica para execução em um único thread), e cada chamada aoborrow_mutrealiza uma verificação em tempo de execução. No entanto, em comparação com a cópia profunda de dados, essas sobrecargas são insignificantes.
📖 Resumo
Box<T>é um ponteiro inteligente alocado na pilha, utilizado para tipos recursivos, grandes conjuntos de dados e objetos de trait — ele garante propriedade exclusiva sem nenhuma sobrecarga adicional.Dereftrait permite que os ponteiros inteligentes suportem o operador de desreferência*, enquanto oDroptrait garante que os recursos sejam automaticamente liberados quando o escopo terminarRc<T>é um ponteiro inteligente com contagem de referências que permite a propriedade compartilhada em um ambiente de thread único —Rc::cloneapenas incrementa a contagem sem copiar os dadosRefCell<T>Oferece mutabilidade interna, adiando as verificações de empréstimo do tempo de compilação para o tempo de execução — violações das regras causam um panicCell<T>Implementar a mutabilidade interna por meio da cópia de valores (requerT: Copy);RefCell<T>implementa isso usando referências (qualquer tipo é permitido)Rc<RefCell<T>>O modo combinado = propriedade compartilhada + mutabilidade interna é a solução flexível de compartilhamento de dados mais comumente utilizada na programação monotreadada em Rust
📝 Exercícios
- Dificuldade ⭐: Escreva um programa que use
Box<T>para criar umaCons Listrecursiva (contendo pelo menos 3 elementos) e, em seguida, imprima a lista inteira. Você deve usar#[derive(Debug)]para imprimir a lista. - Dificuldade ⭐⭐: Implemente um sistema de “nota compartilhada”: use
Rc<RefCell<String>>para permitir que três pessoas (Alice, Bob e Carol) compartilhem uma única sequência de caracteres. Todos podem lê-la e todos podem escrever nela (acrescentar conteúdo). Verifique se todos conseguem ver o conteúdo completo depois que todos tiverem terminado de escrever. - Dificuldade ⭐⭐⭐: Implemente o padrão “Graph Observer”. Defina
trait Observer { fn notify(&self, msg: &str); }e, em seguida, implementeLoggerObserver(que utilizaRefCell<Vec<String>>internamente para registrar mensagens). Em seguida, useRc<RefCell<dyn Observer>>para permitir que vários observadores sejam notificados pelo mesmo sujeito. Na funçãomain, crie dois observadores; faça com que o sujeito envie três mensagens e verifique se ambos os observadores receberam todas as mensagens.