Bỏ qua để đến nội dung

Lỗi Rust E0308: mismatched types (kiểu không khớp)

Lỗi E0308 nghĩa là Rust cần một giá trị thuộc một kiểu, nhưng bạn lại đưa cho nó giá trị thuộc kiểu khác. Có lẽ đây là lỗi bạn sẽ gặp nhiều nhất trong Rust, và thông báo gần như luôn cho bạn biết cả hai kiểu, nên thường sửa rất nhanh. (Bạn đang tìm lỗi khác? Xem tổng quan các lỗi trình biên dịch.)

error[E0308]: mismatched types
--> src/main.rs:6:29
|
6 | let user = User { name: "Ferris" };
| ^^^^^^^^ expected `String`, found `&str`
|
help: try using a conversion method
|
6 | let user = User { name: "Ferris".to_string() };
| ++++++++++++

Thông báo của trình biên dịch luôn bằng tiếng Anh: “mismatched types” nghĩa là “kiểu không khớp”. Đây là chương trình gây ra lỗi đó:

struct User {
name: String,
}
fn main() {
let user = User { name: "Ferris" };
println!("{}", user.name);
}

Mọi thông báo E0308 đều có cùng một dạng: expected A, found B (cần A, nhưng gặp B).

  • Expected là kiểu mà Rust cần ở chỗ đó. Nó đến từ một nơi khác: một chú thích kiểu, kiểu của tham số hoặc kiểu trả về của hàm, một trường của struct, hoặc một nhánh khác của if/match.
  • Found là kiểu của giá trị mà bạn thật sự viết ở đó.

Trong ví dụ, trường name được khai báo là String, nên Rust cần đúng kiểu đó. Nhưng "Ferris" là một chuỗi literal, có kiểu &str. Hai kiểu này có liên quan với nhau, nhưng không phải cùng một kiểu.

Hãy để ý cả các dòng help:. Với E0308, trình biên dịch thường gợi ý đúng cách sửa, như .to_string() ở trên.

Rust không bao giờ âm thầm chuyển đổi giữa các kiểu. Ở một số ngôn ngữ, một con số lặng lẽ biến thành chuỗi, hoặc một số lớn lặng lẽ bị cắt bớt để vừa với kiểu nhỏ hơn. Những phép chuyển đổi ngầm đó gây ra bug thật: mất dữ liệu, kết quả sai, chương trình sập ở chỗ cách rất xa lỗi thực sự.

Vì vậy Rust bắt mọi phép chuyển đổi phải hiện rõ trong code. Bạn phải gõ thêm một chút, nhưng khi đọc lại code về sau, bạn thấy chính xác chỗ nào giá trị đổi kiểu và bước đó có thể thất bại hay không. Xem Kiểu dữ liệu để biết về chính các kiểu.

Mỗi cách sửa bên dưới bắt đầu bằng dạng của lỗi, để bạn nhảy thẳng tới cách khớp với lỗi của mình.

Cách sửa 1: Bỏ dấu chấm phẩy ở cuối

Phần tiêu đề “Cách sửa 1: Bỏ dấu chấm phẩy ở cuối”
error[E0308]: mismatched types
--> src/main.rs:1:46
|
1 | fn total_price(price: u32, quantity: u32) -> u32 {
| ----------- ^^^ expected `u32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
2 | price * quantity;
| - help: remove this semicolon to return this value

Nếu bạn thấy found (), hãy xem dòng cuối của hàm. Trong Rust, biểu thức cuối cùng của một khối không có dấu chấm phẩy chính là giá trị trả về của khối. Thêm ; biến nó thành một câu lệnh, nên hàm trả về () (kiểu “không có gì”) thay vào đó. Trình biên dịch gợi ý: “remove this semicolon to return this value” (bỏ dấu chấm phẩy này để trả về giá trị). Hãy xóa dấu chấm phẩy:

fn total_price(price: u32, quantity: u32) -> u32 {
price * quantity // không có dấu chấm phẩy: giá trị này được trả về
}
fn main() {
println!("Tổng: {}", total_price(250, 3));
}

Kết quả:

Tổng: 750
▶ Tự chạy thử

Chuyện tương tự xảy ra bên trong khối if và các nhánh match. Xem thêm ở Hàm.

Cách sửa 2: Chuyển đổi giữa String và &str

Phần tiêu đề “Cách sửa 2: Chuyển đổi giữa String và &str”

String là chuỗi có chủ sở hữu và có thể mở rộng. &str là một “góc nhìn” mượn vào một đoạn văn bản. Xem Chuỗi để biết đầy đủ. Chuyển đổi theo cả hai chiều đều ngắn gọn:

  • &str sang String: .to_string() hoặc String::from(...).
  • String sang &str: mượn nó bằng & (hoặc gọi .as_str()).

Trường hợp thứ hai hiện ra dưới dạng expected &str, found String, thường là khi bạn truyền một String cho hàm nhận &str.

struct User {
name: String,
}
fn greet(name: &str) {
println!("Xin chào, {name}!");
}
fn main() {
// &str -> String: .to_string() hoặc String::from()
let user = User { name: "Ferris".to_string() };
// String -> &str: mượn bằng & (hoặc gọi .as_str())
greet(&user.name);
greet(user.name.as_str());
let mut tags: Vec<String> = Vec::new();
tags.push(String::from("rust"));
println!("{tags:?}");
}

Kết quả:

Xin chào, Ferris!
Xin chào, Ferris!
["rust"]
▶ Tự chạy thử

Cách sửa 3: Chuyển đổi giữa các kiểu số

Phần tiêu đề “Cách sửa 3: Chuyển đổi giữa các kiểu số”
error[E0308]: mismatched types
--> src/main.rs:3:22
|
3 | let count: i32 = names.len();
| --- ^^^^^^^^^^^ expected `i32`, found `usize`
| |
| expected due to this

i32, i64, u32 và usize đều là các kiểu khác nhau, và Rust không trộn lẫn chúng. Độ dài và chỉ số luôn là usize. Hãy chọn một cách chuyển đổi:

  • .into() (hoặc i64::from(x)) khi phép chuyển đổi không bao giờ làm mất dữ liệu, như i32 sang i64.
  • i64::try_from(x) (hoặc .try_into()) khi giá trị có thể không vừa. Nó trả về một Result, nên bạn quyết định chuyện gì xảy ra khi thất bại.
  • as là phép ép kiểu nhanh. Nó không bao giờ thất bại, nhưng lặng lẽ cắt bớt những giá trị không vừa, nên chỉ dùng khi bạn biết chắc giá trị nhỏ.
fn average(total: i64, count: i64) -> i64 {
total / count
}
fn main() {
let scores: Vec<i32> = vec![80, 95, 70];
let total: i32 = scores.iter().sum();
// i32 -> i64 luôn vừa, nên .into() là đủ
let total: i64 = total.into();
// usize -> i64 có thể không vừa, nên try_from hỏi trước
let count = i64::try_from(scores.len()).unwrap();
println!("Trung bình: {}", average(total, count));
// `as` cũng được, nhưng nó lặng lẽ cắt bớt những giá trị không vừa
let players = scores.len() as i32;
println!("{players} người chơi");
}

Kết quả:

Trung bình: 81
3 người chơi
▶ Tự chạy thử

Nhiều khi cách sửa đơn giản nhất là đổi chú thích kiểu: let count: usize = names.len();. Các trang Ép kiểu và Chuyển đổi kiểu đi sâu hơn.

Cách sửa 4: Bọc trong Some, hoặc lấy giá trị ra khỏi Option

Phần tiêu đề “Cách sửa 4: Bọc trong Some, hoặc lấy giá trị ra khỏi Option”
error[E0308]: mismatched types
--> src/main.rs:3:27
|
3 | let position: usize = names.iter().position(|n| *n == "Ben");
| ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `Option<usize>`
| |
| expected due to this

Option<usize> không phải là usize. Nó là “có thể là một usize, có thể chẳng có gì”. Sự không khớp này xảy ra theo cả hai chiều:

  • Expected T, found Option<T>: bạn phải xử lý trường hợp “không có gì”, bằng match, if let hoặc unwrap_or(default).
  • Expected Option<T>, found T: hàm của bạn trả về Option, nên hãy bọc giá trị lại: Some(10) thay vì 10.
fn find_discount(code: &str) -> Option<u32> {
if code == "SAVE10" {
return Some(10); // T -> Option<T>: bọc nó trong Some
}
None
}
fn main() {
let names = vec!["Ana", "Ben", "Cy"];
// Option<T> -> T: xử lý trường hợp “không tìm thấy”...
match names.iter().position(|n| *n == "Ben") {
Some(i) => println!("Ben ở vị trí {i}"),
None => println!("Không có Ben ở đây"),
}
// ...hoặc chọn một giá trị mặc định bằng unwrap_or
let discount: u32 = find_discount("HELLO").unwrap_or(0);
println!("Giảm giá: {discount}%");
}

Kết quả:

Ben ở vị trí 1
Giảm giá: 0%
▶ Tự chạy thử

Trình biên dịch có thể gợi ý .expect("REASON"). Cách đó cũng chạy, nhưng nó làm chương trình sập khi giá trị là None, nên chỉ dùng khi None thật sự không thể xảy ra. Xem Option.

Cách sửa 5: Cho mọi nhánh if/else và match cùng một kiểu

Phần tiêu đề “Cách sửa 5: Cho mọi nhánh if/else và match cùng một kiểu”
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:3:52
|
3 | let label = if stock > 0 { "in stock" } else { stock };
| ---------- ^^^^^ expected `&str`, found integer
| |
| expected because of this

Khi if hoặc match tạo ra một giá trị, Rust cần biết kiểu duy nhất của giá trị đó. Vì vậy mọi nhánh phải tạo ra cùng một kiểu. Nhánh đầu tiên đặt ra kỳ vọng, và lỗi chỉ vào nhánh không đồng ý. Với match, thông báo sẽ là “match arms have incompatible types” (các nhánh match có kiểu không tương thích). Hãy sửa nhánh “lạc loài” cho khớp:

fn status_text(code: u16) -> String {
match code {
200 => "OK".to_string(),
404 => "Not Found".to_string(),
_ => format!("Mã không xác định {code}"), // nhánh nào cũng là String
}
}
fn main() {
let stock = 0;
let label = if stock > 0 { "còn hàng" } else { "hết hàng" }; // cả hai đều là &str
println!("{label}");
println!("{}", status_text(404));
println!("{}", status_text(418));
}

Kết quả:

hết hàng
Not Found
Mã không xác định 418
▶ Tự chạy thử

Ở đây một nhánh cần format!, vốn tạo ra một String, nên các nhánh khác cũng phải thành String. (“OK” và “Not Found” được giữ nguyên vì đó là tên chuẩn của mã trạng thái HTTP.) Xem if/else và match.

  • Đọc dữ liệu người dùng nhập. let age: u32 = input.trim(); báo “expected u32, found &str”. Văn bản không phải là số. Cách sửa: phân tích (parse) nó: let age: u32 = input.trim().parse().unwrap(); (hoặc xử lý lỗi, xem Result).
  • Push literal vào một Vec<String>. tags.push("rust") báo “expected String, found &str”. Cách sửa: tags.push("rust".to_string()), hoặc dùng Vec<&str> nếu tất cả chuỗi đều là literal.
  • Tính toán với các kiểu số lẫn lộn. total + n với total là i64 và n là i32 gây ra E0308 (và thường có E0277 đi kèm). Cách sửa: chuyển đổi một vế trước: total + i64::from(n).

Kiểm tra nhanh

1. Một hàm ghi -> i32, nhưng lỗi nói "expected i32, found ()". Nguyên nhân khả dĩ nhất là gì?

2. Một hàm nhận &str và bạn có let name = String::from("Ana");. Bạn truyền nó thế nào?

3. Phép chuyển đổi nào không bao giờ lặng lẽ làm mất dữ liệu?

Điểm: 0 / 3