Lỗi
error[E0308]: mismatched types: expected struct `String`, found `&str`
Hoặc ngược lại:
error[E0308]: mismatched types: expected `&str`, found struct `String`
Đây là một trong những rào cản đầu tiên mà các lập trình viên Rust mới hay gặp phải. String và &str trông gần như giống nhau trong mã nguồn — nhưng với trình biên dịch, chúng là hai kiểu hoàn toàn khác nhau. Rust sẽ không tự động chuyển đổi giữa chúng.
Nguyên Nhân
String được cấp phát trên heap và có quyền sở hữu — nó mang dữ liệu theo cùng. &str chỉ là một tham chiếu mượn vào dữ liệu chuỗi tồn tại ở nơi khác. Hai thứ khác nhau. Trình biên dịch áp đặt ranh giới này một cách nghiêm ngặt, vì vậy sự không khớp sẽ xảy ra lỗi lúc biên dịch, không phải lúc chạy.
Các tình huống phổ biến dễ mắc lỗi này:
- Truyền một chuỗi literal (
&str) vào hàm mong đợiString - Truyền một
Stringcó quyền sở hữu vào chỗ hàm cần&str - Gán một literal vào trường struct có kiểu là
String - Trả về một literal từ hàm có kiểu trả về là
String
Cách Sửa Nhanh — Chọn Trường Hợp Của Bạn
Trường hợp 1: Bạn có &str, cần String
Gọi .to_string(), .to_owned(), hoặc String::from():
// Trước — lỗi biên dịch
fn greet(name: String) {
println!("Hello, {}!", name);
}
fn main() {
greet("Alice"); // error: expected String, found &str
}
// Sau — đã sửa
fn main() {
greet("Alice".to_string());
// hoặc
greet("Alice".to_owned());
// hoặc
greet(String::from("Alice"));
}
Cả ba đều cho kết quả giống nhau. Hầu hết code Rust thực tế bạn thấy đều dùng .to_string().
Trường hợp 2: Bạn có String, cần &str
Mượn bằng & hoặc gọi .as_str():
fn print_name(name: &str) {
println!("{}", name);
}
fn main() {
let name = String::from("Alice");
print_name(&name); // deref coercion: &String → &str
print_name(name.as_str()); // tường minh, hiệu quả như nhau
print_name(&name[..]); // cú pháp slice — hoạt động nhưng ít dùng
}
&name hoạt động được vì Rust tự động áp dụng deref coercion từ &String sang &str. Không có việc sao chép dữ liệu. Đây là cách tiếp cận chuẩn mực.
Trường hợp 3: Trường struct có kiểu String, bạn đang gán một literal
struct Config {
host: String,
}
// Lỗi
let c = Config { host: "localhost" }; // expected String, found &str
// Đã sửa
let c = Config { host: "localhost".to_string() };
let c = Config { host: String::from("localhost") };
Trường hợp 4: Kiểu trả về của hàm là String nhưng bạn đang trả về một literal
// Lỗi
fn get_default() -> String {
"unknown" // expected String, found &str
}
// Đã sửa
fn get_default() -> String {
"unknown".to_string()
// hoặc: String::from("unknown")
}
Trường hợp 5: Đẩy các literal vào Vec
// Lỗi
let mut names: Vec = Vec::new();
names.push("Alice"); // expected String, found &str
// Đã sửa
names.push("Alice".to_string());
names.push(String::from("Bob"));
Tốt Hơn Nữa: Nhận &str Trong Các Hàm Của Bạn
Nếu bạn kiểm soát được chữ ký hàm và không cần lưu trữ hay chỉnh sửa chuỗi, hãy đổi kiểu tham số thành &str thay vì String. Nó chấp nhận cả literal lẫn chuỗi có quyền sở hữu thông qua coercion — không cần chuyển đổi gì ở chỗ gọi hàm:
// Hạn chế — người gọi phải truyền một String có quyền sở hữu
fn greet(name: String) { ... }
// Linh hoạt — hoạt động với cả literal lẫn &String
fn greet(name: &str) { ... }
// Cả hai cách gọi này đều biên dịch được mà không cần .to_string():
greet("Alice"); // &str literal — hoạt động
greet(&owned_string); // &String — hoạt động qua coercion
Hướng dẫn API của Rust khuyến nghị dùng &str cho các tham số chuỗi chỉ đọc. Đây là thay đổi nhỏ giúp người gọi tránh được các lần cấp phát bộ nhớ không cần thiết.
Kiểm Tra Kết Quả
Biên dịch lại dự án:
cargo build
Biên dịch thành công trông như sau:
Compiling myproject v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.42s
Không có error[E0308] trong kết quả đầu ra nghĩa là lỗi không khớp kiểu đã được khắc phục. Để kiểm tra nhanh một file, rustc main.rs cũng hoạt động tốt.
Khi Nào Dùng Gì
.to_string()— lựa chọn hàng đầu để chuyển&strsangString. Dễ đọc, chuẩn mực..to_owned()— kết quả tương tự, hoạt động trên bất kỳ kiểuBorrownào. Một số style guide ưa dùng hơn.String::from()— khởi tạo tường minh. Tốt khi bạn muốn rõ ràng về ý định.&stringhoặc.as_str()— chuyển từ owned sang borrowed. Không tốn chi phí, không cấp phát bộ nhớ.
Tham Khảo Nhanh
// &str → String
let s: String = "hello".to_string();
let s: String = "hello".to_owned();
let s: String = String::from("hello");
let s: String = format!("hello"); // dùng khi bạn cần định dạng chuỗi
// String → &str
let r: &str = &owned; // deref coercion
let r: &str = owned.as_str(); // tường minh
let r: &str = &owned[..]; // full slice

