Ownership is Rust's most unique and defining feature, with deep implications throughout the entire language. It enables Rust to guarantee memory safety without a garbage collector — something most other safe languages cannot do.
Quyền sở hữu (ownership) là tính năng đặc trưng và độc đáo nhất của Rust, có tác động sâu sắc đến toàn bộ ngôn ngữ. Nó cho phép Rust đảm bảo an toàn bộ nhớ mà không cần garbage collector — điều mà hầu hết các ngôn ngữ an toàn khác không thể làm được.
All programs must manage memory. Languages like JavaScript, Python, and Go use a garbage collector that automatically frees memory. Others like C and C++ require manual management. Rust takes a third approach: memory is managed through the ownership system, with rules checked at compile time — there is no runtime cost.
Tất cả các chương trình đều phải quản lý bộ nhớ. Một số ngôn ngữ như JavaScript, Python, Go dùng garbage collector tự động giải phóng bộ nhớ. Ngôn ngữ khác như C, C++ yêu cầu lập trình viên quản lý thủ công. Rust chọn con đường thứ ba: bộ nhớ được quản lý qua hệ thống quyền sở hữu với các quy tắc được kiểm tra tại thời điểm biên dịch — không có chi phí runtime.
Understanding the difference between stack and heap is essential for understanding ownership. Both are parts of memory available at runtime, but they have very different structures and characteristics.
Hiểu sự khác biệt giữa stack và heap là điều cần thiết để hiểu về quyền sở hữu. Cả hai đều là vùng bộ nhớ mà chương trình sử dụng tại runtime, nhưng chúng có cấu trúc và đặc tính rất khác nhau.
- Stack stores data in LIFO (Last In, First Out) order. Data size must be known at compile time. Very fast because only the stack pointer needs to move up or down.
Stack lưu trữ dữ liệu theo thứ tự LIFO (Last In, First Out). Kích thước dữ liệu phải biết trước tại thời điểm biên dịch. Rất nhanh vì chỉ cần di chuyển con trỏ stack lên hoặc xuống.
- Heap allocates data at runtime and can vary in size. Slower than stack because it requires finding a large enough free memory region and tracking allocations via pointers.
Heap cấp phát dữ liệu tại runtime, có thể thay đổi kích thước. Chậm hơn stack vì cần tìm vùng bộ nhớ trống đủ lớn và theo dõi qua con trỏ.
- Ownership primarily addresses heap data: tracking what code is using heap data, minimizing duplicate data, and cleaning up unused data.
Quyền sở hữu chủ yếu giải quyết vấn đề dữ liệu trên heap: theo dõi code nào đang dùng dữ liệu heap, giảm thiểu dữ liệu trùng lặp, và dọn dẹp dữ liệu không còn dùng.
1// Stack data: size known at compile time2let x: i32 = 5; // stored entirely on the stack3let y: bool = true; // stored entirely on the stack4let arr: [i32; 3] = [1, 2, 3]; // stored on the stack56// Heap data: size not known at compile time, or can grow7let s = String::from("hello"); // metadata on stack, content on heap8let v = vec![1, 2, 3]; // metadata on stack, elements on heapThe entire Rust ownership system is built on three fundamental rules. Violating any rule produces a compile error. These are the foundation of everything memory-related in Rust.
Toàn bộ hệ thống quyền sở hữu của Rust được xây dựng trên ba quy tắc cơ bản. Vi phạm bất kỳ quy tắc nào sẽ gây ra lỗi biên dịch. Đây là nền tảng của mọi thứ liên quan đến bộ nhớ trong Rust.
- Each value in Rust has a variable called its owner.
Mỗi giá trị trong Rust có một biến được gọi là chủ sở hữu (owner) của nó.
- There can only be one owner at a time.
Tại một thời điểm, chỉ có thể có duy nhất một chủ sở hữu.
- When the owner goes out of scope, the value is dropped and memory is automatically freed.
Khi chủ sở hữu ra khỏi phạm vi (scope), giá trị sẽ bị xóa (dropped) và bộ nhớ được giải phóng tự động.
Ba quy tắc này có vẻ đơn giản nhưng có tác động rất rộng lớn. Chúng là lý do tại sao Rust có thể đảm bảo không có double-free, use-after-free, hay memory leak mà không cần garbage collector.
Scope is the range within a program for which an item is valid. In Rust, when a variable goes out of scope (the closing curly brace), Rust automatically calls the drop function to free that variable's memory.
Phạm vi (scope) là khoảng không gian trong chương trình mà một mục hợp lệ. Trong Rust, khi một biến ra khỏi phạm vi (dấu ngoặc nhọn đóng), Rust tự động gọi hàm drop để giải phóng bộ nhớ của biến đó.
1fn main() {2 // s is not valid here — not yet declared34 {5 let s = String::from("hello"); // s is valid from this point67 println!("{s}"); // do stuff with s8 }9 // The scope is now over. Rust calls drop(s) automatically.10 // s is no longer valid — the heap memory is freed.1112 // println!("{s}"); // ERROR: use of possibly-uninitialized `s`1314 // This same pattern applies to any type that owns heap resources:15 {16 let v = vec![1, 2, 3];17 println!("{v:?}");18 } // v is dropped here — Vec frees its heap memory19}To understand ownership more deeply, we need a more complex data type. String is the perfect example because its size is not known at compile time and it is stored on the heap. This differs from string literals (&str), which are immutable and embedded directly in the binary.
Để hiểu quyền sở hữu sâu hơn, chúng ta cần một kiểu dữ liệu phức tạp hơn. String là ví dụ hoàn hảo vì nó có kích thước không biết trước tại thời điểm biên dịch và được lưu trên heap. Khác với string literal (&str) là bất biến và được nhúng trực tiếp vào binary.
1fn main() {2 // String literal: immutable, hardcoded in the binary, type is &str3 let s1: &str = "hello";45 // String: mutable, heap-allocated, growable, type is String6 let mut s2 = String::from("hello");7 s2.push_str(", world!"); // appends to the String8 s2.push('!'); // appends a single character910 println!("{s1}"); // hello11 println!("{s2}"); // hello, world!!1213 // String has three parts stored on the stack:14 // 1. ptr — pointer to the heap memory holding the content15 // 2. len — current length in bytes16 // 3. capacity — total bytes allocated on the heap17 println!("len={}, capacity={}", s2.len(), s2.capacity());18} // s2 goes out of scope; drop is called; heap memory is freed19 // s1 has no heap memory to freeWhen you assign a heap-allocated variable to another, Rust moves ownership — the original variable becomes invalid. This prevents double-free errors: only the new owner drops the data when it goes out of scope.
Khi bạn gán một biến heap-allocated cho biến khác, Rust di chuyển quyền sở hữu — biến cũ trở nên không hợp lệ. Điều này ngăn chặn lỗi double-free: chỉ có chủ sở hữu mới mới drop dữ liệu khi ra khỏi phạm vi.
1fn main() {2 let s1 = String::from("hello");3 let s2 = s1; // s1 is MOVED to s2 — s1 is now invalid45 // println!("{s1}"); // ERROR: borrow of moved value: `s1`6 println!("{s2}"); // OK — s2 is the new owner78 // Why not a "shallow copy"?9 // Rust invalidates s1 to prevent BOTH s1 and s2 from trying10 // to free the same heap memory when they go out of scope.1112 // Stack-only types (Copy types) behave differently:'Move' trong Rust không có nghĩa là dữ liệu heap bị di chuyển vật lý trong bộ nhớ. Chỉ có metadata trên stack (pointer, length, capacity) được sao chép, và biến cũ bị vô hiệu hóa. Hoàn toàn không có chi phí runtime.
If you want to deeply copy the heap data of a String, use the clone method. This is a computationally expensive operation — when you see clone in code, you know that some potentially costly code is being executed.
Nếu bạn muốn sao chép sâu (deep copy) dữ liệu heap của một String, dùng phương thức clone. Đây là một thao tác tốn kém về mặt tính toán — khi bạn thấy clone trong code, bạn biết rằng code tùy ý có thể tốn nhiều tài nguyên đang được thực thi.
1fn main() {2 let s1 = String::from("hello");3 let s2 = s1.clone(); // deep copy — duplicates heap data45 println!("s1={s1}, s2={s2}"); // Both valid — s1 was not moved6}Types stored entirely on the stack implement the Copy trait and are automatically copied on assignment — no move occurs. A type can implement Copy if and only if all of its components also implement Copy.
Các kiểu lưu hoàn toàn trên stack triển khai trait Copy và được sao chép tự động khi gán — không có move. Một kiểu có thể triển khai Copy nếu và chỉ nếu tất cả các thành phần của nó đều triển khai Copy.
1fn main() {2 // These types all implement Copy:3 let a: i32 = 5; let b = a; // copied4 let c: f64 = 3.14; let d = c; // copied5 let e: bool = true; let f = e; // copied6 let g: char = 'z'; let h = g; // copied78 // Tuples implement Copy only if all fields implement Copy:9 let t1: (i32, f64) = (1, 2.0);10 let t2 = t1; // copied — both i32 and f64 are Copy1112 // Fixed-size arrays of Copy types also implement Copy:Passing a value to a function follows the same semantics as assigning to a variable: passing a String moves it, passing an i32 copies it. After the function takes ownership, the original variable is no longer valid.
Truyền một giá trị vào hàm tuân theo cùng ngữ nghĩa như gán cho biến: truyền String sẽ move nó, truyền i32 sẽ copy nó. Sau khi hàm lấy quyền sở hữu, biến ban đầu không còn hợp lệ.
1fn main() {2 let s = String::from("hello");34 takes_ownership(s); // s's value moves into the function5 // println!("{s}"); // ERROR: s was moved — no longer valid here67 let x = 5;8 makes_copy(x); // i32 is Copy — x is still valid9 println!("{x}"); // OK10}1112fn takes_ownership(some_string: String) {13 println!("{some_string}");14} // some_string goes out of scope; drop is called; heap memory freed1516fn makes_copy(some_integer: i32) {17 println!("{some_integer}");18} // some_integer goes out of scope — nothing special happens (no heap)Returning values from a function also transfers ownership. The pattern of taking ownership and returning it is cumbersome. This is exactly why Rust has references: they let a function use a value without taking ownership — the topic of the next chapter.
Trả về giá trị từ hàm cũng chuyển quyền sở hữu. Mẫu lấy quyền sở hữu rồi trả lại khá cồng kềnh. Đây là lý do tại sao Rust có tham chiếu (references): cho phép hàm sử dụng giá trị mà không lấy quyền sở hữu — chủ đề của chương tiếp theo.
1fn main() {2 // gives_ownership transfers its return value to s13 let s1 = gives_ownership();4 println!("{s1}");56 let s2 = String::from("hello");78 // s2 is moved into takes_and_gives_back9 // which also returns a value, moved into s310 let s3 = takes_and_gives_back(s2);11 // println!("{s2}"); // ERROR: s2 was moved12 println!("{s3}");Mẫu lấy quyền sở hữu và trả lại trong calculate_length_verbose rất cồng kềnh. Trong thực tế, bạn dùng tham chiếu (&String) để hàm có thể đọc dữ liệu mà không lấy quyền sở hữu. Tham chiếu là chủ đề của chương References and Borrowing.
Key Takeaways
Điểm Chính
- Each value in Rust has exactly one ownerMỗi giá trị trong Rust có đúng một chủ sở hữu
- When the owner goes out of scope, the value is droppedKhi chủ sở hữu ra khỏi phạm vi, giá trị sẽ được giải phóng
- Assignment moves ownership by default for heap dataGán giá trị di chuyển quyền sở hữu mặc định cho dữ liệu trên heap
- Types implementing Copy are duplicated instead of movedCác kiểu triển khai Copy được sao chép thay vì di chuyển
Practice
Test your understanding of this chapter
Which of the following is NOT one of Rust's three ownership rules?
Điều nào sau đây KHÔNG phải là một trong ba quy tắc quyền sở hữu của Rust?
What happens when you assign a String to another variable (let s2 = s1)?
Điều gì xảy ra khi bạn gán một String cho biến khác (let s2 = s1)?
Stack-only types like i32 and bool implement the Copy trait, so assigning them to another variable does not move ownership.
Các kiểu chỉ lưu trên stack như i32 và bool triển khai trait Copy, nên khi gán cho biến khác không chuyển quyền sở hữu.
Fix the double-use error
Sửa lỗi sử dụng biến đã chuyển quyền sở hữu
fn main() { let s1 = String::from("hello"); let s2 = s1.; println!("{s1}, {s2}"); }
Rust automatically calls the drop function and frees heap memory when a variable goes out of scope — no garbage collector or manual free is needed.
Rust tự động gọi hàm drop và giải phóng bộ nhớ heap khi biến ra khỏi phạm vi — không cần garbage collector hay giải phóng thủ công.