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.)
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”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);}Ý nghĩa
Phần tiêu đề “Ý nghĩa”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.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”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.
Cách sửa
Phần tiêu đề “Cách sửa”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` expression2 | price * quantity; | - help: remove this semicolon to return this valueNế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: 750Chuyệ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:
&strsangString:.to_string()hoặcString::from(...).Stringsang&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"]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 thisi32, 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ặci64::from(x)) khi phép chuyển đổi không bao giờ làm mất dữ liệu, nhưi32sangi64.i64::try_from(x)(hoặc.try_into()) khi giá trị có thể không vừa. Nó trả về mộtResult, nên bạn quyết định chuyện gì xảy ra khi thất bại.aslà 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: 813 người chơiNhiề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 thisOption<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, foundOption<T>: bạn phải xử lý trường hợp “không có gì”, bằngmatch,if lethoặcunwrap_or(default). - Expected
Option<T>, foundT: 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í 1Giảm giá: 0%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 thisKhi 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àngNot FoundMã không xác định 418Ở đâ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.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”- Đọc dữ liệu người dùng nhập.
let age: u32 = input.trim();báo “expectedu32, 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 “expectedString, found&str”. Cách sửa:tags.push("rust".to_string()), hoặc dùngVec<&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 + nvớitotallài64vànlài32gâ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).
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0277: trait bound not satisfied: hay xuất hiện cạnh E0308, ví dụ khi bạn cộng một
i64với mộti32. - E0599: no method found: bạn gọi một phương thức không tồn tại trên kiểu này, thường vì giá trị có kiểu khác với bạn nghĩ.
- E0425: cannot find value in this scope: một cái tên mà Rust hoàn toàn không biết.
- Tất cả lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”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
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0308 — Rust error codes index (tiếng Anh): phần giải thích chính thức của lỗi này.
- Functions with Return Values — The Rust Book (tiếng Anh): vì sao một dấu chấm phẩy ở cuối làm thay đổi giá trị hàm trả về.
- Storing UTF-8 Text with Strings — The Rust Book (tiếng Anh):
Stringvà&strliên quan với nhau thế nào và cách chuyển đổi giữa chúng. - Conversion — Rust by Example (tiếng Anh):
From,Into,TryFromvàTryIntovới các ví dụ ngắn. - Option — thư viện chuẩn Rust (tiếng Anh): mọi phương thức để lấy giá trị ra khỏi một
Option, nhưunwrap_orvàmap.