🔤 Chuỗi Ký Tự - String vs &str
Strings Trong Rust
Rust có HAI kiểu string chính:
String- Owned, có thể thay đổi, trên heap&str- Borrowed, không thay đổi, string slice
Đây là điểm khó nhất cho người mới! 😅
Giải Thích Cho Bạn 5 Tuổi
Tưởng tượng bạn có cuốn sách:
📕 String = Cuốn sách BẠN SỞ HỮU
- Bạn có thể viết thêm
- Bạn có thể xóa trang
- Bạn có thể cho người khác mượn
- Bạn quyết định khi nào vứt đi
📖 &str = MƯỢN sách để đọc
- Chỉ được đọc, không được viết
- Không thể xé trang
- Phải trả lại chủ
- Không quyết định khi nào vứt
String = Chủ nhân, &str = Người mượn! 🎭
📕 String Literals (&str)
Khai Báo
fn main() {
let s1 = "Hello"; // Type: &str
let s2: &str = "World";
println!("{} {}", s1, s2);
}
Đặc điểm:
- ✅ Lưu trong binary (part of program)
- ✅ Immutable (không đổi)
- ✅ Nhanh và nhẹ
- ✅ Dùng nhiều nhất cho text cố định
String Literals Ở Đâu?
fn main() {
// Các string literals này lưu trong binary
let greeting = "Xin chào";
let farewell = "Tạm biệt";
// Khi compile, chúng embedded vào executable
}
Memory layout:
┌─────────────────┐
│ Your Program │
├─────────────────┤
│ Code (.text) │
├─────────────────┤
│ Data (.rodata) │ ← String literals ở đây!
│ "Xin chào" │
│ "Tạm biệt" │
└─────────────────┘
📗 String Type
Tạo String
fn main() {
// From string literal
let s1 = String::from("Hello");
// Using to_string()
let s2 = "World".to_string();
// Empty string
let s3 = String::new();
println!("s1: {}", s1);
println!("s2: {}", s2);
println!("s3: '{}'", s3);
}
Kết quả:
s1: Hello
s2: World
s3: ''
String Trên Heap
fn main() {
let s = String::from("Hello");
// String data lưu trên heap!
}
Memory layout:
Stack Heap
┌──────────┐ ┌─────────────┐
│ ptr │─────────→ │ H e l l o │
│ len: 5 │ └─────────────┘
│ capacity │
└──────────┘
Tại Sao Hai Loại?
| Feature | String | &str |
|---|---|---|
| Ownership | Owned | Borrowed |
| Mutable | ✅ Có thể | ❌ Không |
| Memory | Heap | Stack/Binary |
| Size | Có thể thay đổi | Fixed |
| Cost | Allocation | Cheap |
| Khi nào dùng | Cần modify | Read-only |
🔄 Chuyển Đổi Giữa String và &str
&str → String
fn main() {
let s1: &str = "Hello";
// Cách 1: String::from()
let s2: String = String::from(s1);
// Cách 2: .to_string()
let s3: String = s1.to_string();
// Cách 3: .to_owned()
let s4: String = s1.to_owned();
println!("{}, {}, {}", s2, s3, s4);
}
String → &str
fn main() {
let s1: String = String::from("Hello");
// Cách 1: Borrow với &
let s2: &str = &s1;
// Cách 2: .as_str()
let s3: &str = s1.as_str();
println!("{}, {}", s2, s3);
}
Ví dụ thực tế:
fn main() {
let owned = String::from("Rust");
// Pass as &str
print_str(&owned); // ✅ OK - &String → &str tự động
// Keep ownership
println!("Still have: {}", owned);
}
fn print_str(s: &str) {
println!("Received: {}", s);
}