Smart pointers are data structures that act like pointers but also have additional metadata and capabilities for memory management. Unlike regular references that only borrow data, smart pointers usually own the data they point to.
Con trỏ thông minh là các cấu trúc dữ liệu hoạt động như con trỏ nhưng cũng có thêm metadata và khả năng quản lý bộ nhớ. Không giống như tham chiếu thông thường chỉ mượn dữ liệu, con trỏ thông minh thường sở hữu dữ liệu chúng trỏ đến.
Rust has several built-in smart pointers in the standard library: Box<T>, Rc<T>, Arc<T>, RefCell<T>, and more. Each solves different problems related to memory ownership and concurrency.
Rust có một số con trỏ thông minh tích hợp sẵn trong thư viện chuẩn: Box<T>, Rc<T>, Arc<T>, RefCell<T> và nhiều loại khác. Mỗi loại giải quyết các vấn đề khác nhau liên quan đến sở hữu bộ nhớ và đồng thời.
Box<T> is the most straightforward smart pointer in Rust. It lets you store data on the heap rather than the stack. The stack holds only the pointer to the heap data. When a Box goes out of scope, the heap data is also deallocated.
Box<T> là con trỏ thông minh đơn giản nhất trong Rust. Nó cho phép bạn lưu trữ dữ liệu trên heap thay vì stack. Trên stack chỉ lưu trữ con trỏ đến dữ liệu trên heap. Khi Box ra khỏi phạm vi, dữ liệu heap cũng bị giải phóng.
1fn main() {2 // Store an i32 on the heap3 let b = Box::new(5);4 println!("b = {}", b); // b = 556 // Box is dereferenced automatically7 let x = *b + 1; // x = 689 // b is dropped here, heap memory freed10}1112// Boxes are useful for large data you want to move13// without copying the whole structure14fn process(data: Box<[u8; 1_000_000]>) {15 // Only a pointer (8 bytes) is moved, not 1MB of data16 println!("First byte: {}", data[0]);17}There are three most common use cases for Box: when you have a type whose size cannot be determined at compile time, when you want to transfer ownership of large data without copying, and when you want to own a value you only care about as a trait implementor.
Có ba trường hợp sử dụng Box phổ biến nhất: khi bạn có kiểu dữ liệu mà kích thước không xác định tại compile time, khi bạn muốn chuyển quyền sở hữu dữ liệu lớn mà không sao chép, và khi bạn muốn sở hữu một giá trị mà bạn chỉ quan tâm đến trait của nó.
1// Use case 1: Recursive types (size unknown at compile time)2// This would cause a compile error WITHOUT Box:3// enum List { Cons(i32, List), Nil }45// Correct version using Box:6enum List {7 Cons(i32, Box<List>),8 Nil,9}1011fn main() {12 let list = List::Cons(The Deref trait lets you customize the behavior of the dereference operator *. By implementing Deref, a smart pointer can be used just like a regular reference. Deref coercion is the automatic conversion of a smart pointer reference into a reference to the inner type.
Trait Deref cho phép bạn tùy chỉnh hành vi của toán tử tham chiếu * (dereference). Bằng cách implement Deref, một con trỏ thông minh có thể được sử dụng giống như một tham chiếu thông thường. Ép kiểu Deref (Deref coercion) là tính năng tự động chuyển đổi tham chiếu của con trỏ thông minh thành tham chiếu của kiểu bên trong.
1use std::ops::Deref;23struct MyBox<T>(T);45impl<T> MyBox<T> {6 fn new(x: T) -> MyBox<T> {7 MyBox(x)8 }9}1011impl<T> Deref for MyBox<T> {12 type Target = T;Ép kiểu Deref xảy ra tự động khi bạn truyền một tham chiếu đến một hàm mà kiểu tham số không khớp. Rust sẽ gọi deref() nhiều lần nếu cần để khớp kiểu.
The Drop trait lets you customize what happens when a value is about to go out of scope. It is similar to destructors in other languages. Rust automatically calls drop() when a variable goes out of scope, in the reverse order of declaration.
Trait Drop cho phép bạn tùy chỉnh những gì xảy ra khi một giá trị sắp ra khỏi phạm vi. Nó tương tự như destructors trong các ngôn ngữ khác. Rust tự động gọi drop() khi biến ra khỏi phạm vi, theo thứ tự ngược lại với thứ tự khai báo.
1struct Resource {2 name: String,3}45impl Drop for Resource {6 fn drop(&mut self) {7 println!("Dropping resource: {}", self.name);8 }9}1011fn main() {12 let r1 = Resource { name: String::from("first") };Bạn không thể gọi trực tiếp phương thức drop() của Deref — thay vào đó hãy dùng std::mem::drop(). Nếu không, Rust sẽ báo lỗi biên dịch để ngăn việc giải phóng bộ nhớ hai lần.
Rc<T> (Reference Counted) enables multiple owners of the same data within a single thread. It tracks the number of references to a value and frees the memory only when no references remain. Rc<T> is only for single-threaded scenarios.
Rc<T> (Reference Counted) cho phép nhiều chủ sở hữu của cùng một dữ liệu trong một luồng đơn. Nó theo dõi số lượng tham chiếu đến một giá trị và chỉ giải phóng bộ nhớ khi không còn tham chiếu nào. Rc<T> chỉ dùng được trong các trường hợp đơn luồng.
1use std::rc::Rc;23fn main() {4 let a = Rc::new(String::from("shared data"));56 println!("Reference count after creating a: {}", Rc::strong_count(&a));7 // Output: 189 let b = Rc::clone(&a); // Increments count, does NOT deep copy10 println!("Reference count after cloning to b: {}", Rc::strong_count(&a));11 // Output: 212Arc<T> (Atomically Reference Counted) is like Rc<T> but safe to use across multiple threads. The reference counting is done with atomic operations, ensuring thread safety. Arc has slightly higher performance cost than Rc.
Arc<T> (Atomically Reference Counted) giống như Rc<T> nhưng an toàn để sử dụng giữa nhiều luồng. Việc đếm tham chiếu được thực hiện bằng các phép toán nguyên tử, đảm bảo an toàn trong môi trường đa luồng. Arc có chi phí hiệu suất cao hơn một chút so với Rc.
1use std::sync::Arc;2use std::thread;34fn main() {5 let data = Arc::new(vec![1, 2, 3, 4, 5]);67 let mut handles = vec![];89 for i in 0..3 {10 let data_clone = Arc::clone(&data);11 let handle = thread::spawn(move || {12 println!(Dùng Rc<T> khi bạn chỉ làm việc trong một luồng — nó nhanh hơn Arc<T>. Chỉ dùng Arc<T> khi bạn cần chia sẻ dữ liệu giữa nhiều luồng.
RefCell<T> allows you to mutate data even when there are immutable references to it — a concept called interior mutability. Unlike Box<T> and Rc<T> which enforce borrowing rules at compile time, RefCell<T> enforces them at runtime.
RefCell<T> cho phép bạn biến đổi dữ liệu ngay cả khi có các tham chiếu bất biến đến dữ liệu đó — một khái niệm gọi là tính biến đổi nội tại. Không giống như Box<T> và Rc<T> kiểm tra quy tắc mượn tại compile time, RefCell<T> kiểm tra tại runtime.
1use std::cell::RefCell;23fn main() {4 let data = RefCell::new(vec![1, 2, 3]);56 // Immutable borrow7 {8 let v = data.borrow();9 println!("Data: {:?}", v);10 } // immutable borrow released here1112 // Mutable borrowNếu bạn vi phạm quy tắc mượn với RefCell<T> (ví dụ: hai lần borrow_mut()), chương trình sẽ panic tại runtime thay vì báo lỗi tại compile time.
Combining Rc<T> and RefCell<T> creates a value with multiple owners that also allows mutation. This is a common pattern when you need to share mutable data across multiple parts of a program without multithreading.
Kết hợp Rc<T> và RefCell<T> tạo ra một giá trị có thể có nhiều chủ sở hữu và cho phép biến đổi. Đây là mẫu phổ biến khi bạn cần chia sẻ dữ liệu có thể thay đổi giữa nhiều phần của chương trình mà không cần đa luồng.
1use std::cell::RefCell;2use std::rc::Rc;34#[derive(Debug)]5struct Node {6 value: i32,7 children: Vec<Rc<RefCell<Node>>>,8}910impl Node {11 fn new(value: i32) -> Rc<RefCell<Node>> {12 Rc::new(RefCell::new(Node {Weak<T> is a weak reference to data managed by an Rc<T>. Unlike Rc::clone(), Weak::clone() does not increment the strong count, meaning it does not prevent the data from being freed. You call upgrade() to get an Option<Rc<T>> from a Weak.
Weak<T> là một tham chiếu yếu đến dữ liệu được quản lý bởi Rc<T>. Khác với Rc::clone(), Weak::clone() không tăng strong count, nghĩa là nó không ngăn dữ liệu bị giải phóng. Bạn cần gọi upgrade() để lấy Option<Rc<T>> từ Weak.
1use std::cell::RefCell;2use std::rc::{Rc, Weak};34#[derive(Debug)]5struct Node {6 value: i32,7 parent: RefCell<Weak<Node>>,8 children: RefCell<Vec<Rc<Node>>>,9}1011fn main() {12 let parent = Rc::new(Node {Although Rust prevents most memory errors, it is still possible to create memory leaks with Rc<T> through reference cycles. When two Rc values point to each other, neither strong count will ever reach 0 and the memory will never be freed.
Mặc dù Rust ngăn chặn hầu hết các lỗi bộ nhớ, nhưng vẫn có thể tạo ra rò rỉ bộ nhớ bằng Rc<T> thông qua các chu trình tham chiếu. Khi hai giá trị Rc trỏ đến nhau, strong count của mỗi cái sẽ không bao giờ về 0 và bộ nhớ sẽ không được giải phóng.
1use std::cell::RefCell;2use std::rc::Rc;34// BAD: Reference cycle causes memory leak5#[derive(Debug)]6enum BadList {7 Cons(i32, RefCell<Rc<BadList>>),8 Nil,9}1011// GOOD: Use Weak to break cycles12use std::rc::Weak;Quy tắc chung: dùng Rc::clone() (strong reference) cho quan hệ sở hữu, và dùng Rc::downgrade() (weak reference) cho quan hệ ngược lại hoặc quan hệ ngang hàng để tránh chu trình tham chiếu.
- Box<T>: Heap allocation, single owner, stack-stored pointer to heap data
Box<T>: Cấp phát heap, sở hữu đơn, kích thước biết tại compile time trên stack
- Rc<T>: Reference counting, multiple owners, single-threaded only, immutable
Rc<T>: Đếm tham chiếu, nhiều chủ sở hữu, chỉ đơn luồng, bất biến
- Arc<T>: Like Rc but thread-safe via atomic counting
Arc<T>: Giống Rc nhưng an toàn đa luồng nhờ đếm nguyên tử
- RefCell<T>: Interior mutability, borrow checking at runtime
RefCell<T>: Tính biến đổi nội tại, kiểm tra mượn tại runtime
- Rc<RefCell<T>>: Multiple owners + mutable, single-threaded
Rc<RefCell<T>>: Nhiều chủ sở hữu + có thể biến đổi, đơn luồng
- Arc<Mutex<T>>: Multiple owners + mutable, multi-threaded
Arc<Mutex<T>>: Nhiều chủ sở hữu + có thể biến đổi, đa luồng
- Weak<T>: Non-owning reference that does not prevent deallocation, used to break cycles
Weak<T>: Tham chiếu yếu không ngăn giải phóng bộ nhớ, dùng để phá chu trình
Key Takeaways
Điểm Chính
- Box<T> allocates data on the heap with single ownershipBox<T> cấp phát dữ liệu trên heap với một chủ sở hữu
- Rc<T> enables multiple owners via reference counting (single-threaded)Rc<T> cho phép nhiều chủ sở hữu qua đếm tham chiếu (đơn luồng)
- RefCell<T> enforces borrowing rules at runtime instead of compile timeRefCell<T> thực thi quy tắc mượn tại thời điểm chạy thay vì biên dịch
- Arc<T> is the thread-safe version of Rc<T> using atomic operationsArc<T> là phiên bản an toàn luồng của Rc<T> dùng thao tác nguyên tử
Practice
Test your understanding of this chapter
What is the key difference between Rc<T> and Arc<T>?
Sự khác biệt chính giữa Rc<T> và Arc<T> là gì?
What happens if you call borrow_mut() on a RefCell<T> while an active borrow_mut() already exists?
Điều gì xảy ra nếu bạn gọi borrow_mut() trên RefCell<T> khi đã có một borrow_mut() đang hoạt động?
You can call the drop() method directly on a value (e.g., value.drop()) to explicitly free it early.
Bạn có thể gọi trực tiếp phương thức drop() trên một giá trị (ví dụ: value.drop()) để giải phóng bộ nhớ sớm.
A Weak<T> reference increments the strong count of an Rc<T>, keeping the data alive as long as the Weak reference exists.
Một tham chiếu Weak<T> tăng strong count của Rc<T>, giữ dữ liệu còn sống miễn là tham chiếu Weak tồn tại.
Downgrade an Rc to a Weak reference to break a reference cycle
Hạ cấp Rc thành tham chiếu Weak để phá chu trình tham chiếu
let strong = Rc::new(5); let weak: Weak<i32> = Rc::();