Rust 错误 E0308:mismatched types(类型不匹配)
E0308 的意思是:Rust 需要某种类型的值,你给它的却是另一种类型的值。这大概是你在 Rust 里最常见到的错误。好在报错信息几乎总会同时告诉你两个类型,所以通常很快就能修好。(想找别的错误?请看编译错误总览。)
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() }; | ++++++++++++产生这个错误的程序是这样的:
struct User { name: String,}
fn main() { let user = User { name: "Ferris" }; println!("{}", user.name);}编译器说:类型不匹配,这里需要 String,找到的却是 &str(“expected String, found &str”)。help: 建议你用一个转换方法,也就是加上 .to_string()。
这个错误是什么意思
Section titled “这个错误是什么意思”每条 E0308 报错都是同一个样子:expected A, found B(需要 A,找到的是 B)。
- expected(需要的)是 Rust 在这个位置要求的类型。它来自别处:类型注解、函数的参数类型或返回类型、结构体字段,或者
if/match的另一个分支。 - found(找到的)是你实际写在这里的值的类型。
在这个例子里,name 字段声明为 String,所以 Rust 需要的就是 String。但 "Ferris" 是一个字符串字面量,它的类型是 &str。两者有关系,但不是同一个类型。
也要留意 help: 那几行。遇到 E0308 时,编译器常常会直接给出准确的修复方法,比如上面的 .to_string()。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”Rust 从不悄悄地在类型之间转换。在有些语言里,数字会悄悄变成字符串,或者一个大数会被悄悄截断,好塞进更小的类型。这些看不见的转换会造成真实的 bug:数据丢失、结果出错,还有在离真正错误很远的地方发生的崩溃。
所以 Rust 让每一次转换都在代码里看得见。要多敲几个字,但以后读代码时,你能清楚地看到值在哪里换了类型,以及这一步会不会失败。类型本身的介绍见数据类型。
下面每个修复都先给出报错的样子,你可以直接跳到和你的报错对应的那一个。
修复 1:去掉末尾的分号
Section titled “修复 1:去掉末尾的分号”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 value如果你看到 found (),就去检查函数的最后一行。在 Rust 里,代码块中最后一个不带分号的表达式就是它的返回值。加上 ; 会把它变成一条语句,于是函数返回的是 ()(表示“什么都没有”的类型)。编译器也提示了:函数体没有末尾表达式,所以隐式返回了 ();删掉这个分号就能返回这个值。把分号删掉:
fn total_price(price: u32, quantity: u32) -> u32 { price * quantity // 没有分号:返回这个值}
fn main() { println!("总价:{}", total_price(250, 3));}输出:
总价:750if 代码块和 match 分支里也会发生同样的事。详见函数。
修复 2:在 String 和 &str 之间转换
Section titled “修复 2:在 String 和 &str 之间转换”String 是拥有所有权、可以增长的字符串。&str 是借用来的一段文本的视图。完整的介绍见字符串。两个方向的转换都很简短:
&str转String:.to_string()或String::from(...)。String转&str:用&借用它(或者调用.as_str())。
第二种情况的报错是 expected &str, found String,通常出现在你把 String 传给一个接收 &str 的函数时。
struct User { name: String,}
fn greet(name: &str) { println!("你好,{name}!");}
fn main() { // &str -> String:.to_string() 或 String::from() let user = User { name: "Ferris".to_string() };
// String -> &str:用 & 借用(或者调用 .as_str()) greet(&user.name); greet(user.name.as_str());
let mut tags: Vec<String> = Vec::new(); tags.push(String::from("rust")); println!("{tags:?}");}输出:
你好,Ferris!你好,Ferris!["rust"]修复 3:在数字类型之间转换
Section titled “修复 3:在数字类型之间转换”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 和 usize 都是不同的类型,Rust 不会把它们混在一起用。长度和索引永远是 usize。选一种转换方式:
.into()(或i64::from(x)):转换永远不会丢失数据时用,比如i32转i64。i64::try_from(x)(或.try_into()):值可能放不下时用。它返回一个Result,由你决定失败时怎么办。as是快捷的类型转换。它永远不会失败,但放不下的值会被悄悄截断,所以只在你确定值很小时才用。
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 一定放得下,所以 .into() 就够了 let total: i64 = total.into();
// usize -> i64 可能放不下,所以用 try_from 先问一下 let count = i64::try_from(scores.len()).unwrap();
println!("平均分:{}", average(total, count));
// `as` 也行,但放不下的值会被悄悄截断 let players = scores.len() as i32; println!("{players} 名玩家");}输出:
平均分:813 名玩家很多时候,最简单的修复是改一下类型注解:let count: usize = names.len();。类型转换(as)和 From、Into 等转换 trait这两页讲得更深入。
修复 4:用 Some 包起来,或者把 Option 解开
Section titled “修复 4:用 Some 包起来,或者把 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> 不是 usize。它的意思是“也许是一个 usize,也许什么都没有”。这种不匹配有两个方向:
- expected
T, foundOption<T>:你得处理“什么都没有”的情况,用match、if let或unwrap_or(默认值)。 - expected
Option<T>, foundT:你的函数返回Option,所以要把值包起来:写Some(10),而不是10。
fn find_discount(code: &str) -> Option<u32> { if code == "SAVE10" { return Some(10); // T -> Option<T>:用 Some 包起来 } None}
fn main() { let names = vec!["Ana", "Ben", "Cy"];
// Option<T> -> T:处理“没找到”的情况…… match names.iter().position(|n| *n == "Ben") { Some(i) => println!("Ben 的位置是 {i}"), None => println!("Ben 不在这里"), }
// ……或者用 unwrap_or 选一个默认值 let discount: u32 = find_discount("HELLO").unwrap_or(0); println!("折扣:{discount}%");}输出:
Ben 的位置是 1折扣:0%位置从 0 开始数,所以 "Ben" 的位置是 1。编译器可能会建议你用 .expect("REASON")。这样也行,但值是 None 时程序会崩溃,所以只在 None 真的不可能出现时才用它。参见 Option。
修复 5:让 if/else 和 match 的每个分支类型都一样
Section titled “修复 5:让 if/else 和 match 的每个分支类型都一样”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当 if 或 match 产生一个值时,Rust 需要知道这个值唯一的类型。所以每个分支都必须产生同一种类型。第一个分支定下了期望,报错则指向和它不一致的那个分支。对 match 来说,报错写的是“match arms have incompatible types”(match 分支的类型不兼容)。让那个不合群的分支和其他分支一致:
fn status_text(code: u16) -> String { match code { 200 => "OK".to_string(), 404 => "Not Found".to_string(), _ => format!("未知状态码 {code}"), // 每个分支都是 String }}
fn main() { let stock = 0; let label = if stock > 0 { "有货" } else { "已售罄" }; // 两边都是 &str println!("{label}");
println!("{}", status_text(404)); println!("{}", status_text(418));}输出:
已售罄Not Found未知状态码 418这里有一个分支需要用 format!,它产生的是 String,所以其他分支也都改成了 String。("OK" 和 "Not Found" 是 HTTP 的标准状态文字,所以保留英文。)参见 if…else和 match。
- 读取用户输入。
let age: u32 = input.trim();会报“expectedu32, found&str”。文本不是数字。修复: 先解析它:let age: u32 = input.trim().parse().unwrap();(或者处理错误,见 Result)。 - 把字面量 push 进
Vec<String>。tags.push("rust")会报“expectedString, found&str”。修复: 写成tags.push("rust".to_string());如果里面全是字面量,也可以把它改成Vec<&str>。 - 混用不同数字类型做运算。
total是i64、n是i32时,total + n会报 E0308(旁边常常还有 E0277)。修复: 先转换其中一边:total + i64::from(n)。
- E0277:trait bound not satisfied:经常和 E0308 一起出现,比如你把一个
i64和一个i32相加时。 - E0599:no method found:你调用了这个类型上不存在的方法,常常是因为值的类型和你以为的不一样。
- E0425:cannot find value in this scope:一个 Rust 完全不认识的名字。
- 所有编译错误:错误页面的完整列表。
小测验
1. 函数声明了 -> i32,报错却说“expected i32, found ()”。最可能的原因是什么?
2. 一个函数接收 &str,而你有 let name = String::from("Ana");。怎么把它传进去?
3. 哪种转换永远不会悄悄丢失数据?
得分: 0 / 3
- E0308 — Rust error codes index(英文):这个错误的官方解释。
- 具有返回值的函数 — Rust 程序设计语言:为什么末尾多一个分号,函数的返回值就变了。
- 使用字符串储存 UTF-8 编码的文本 — Rust 程序设计语言:
String和&str是什么关系,以及怎么互相转换。 - 类型转换 — 通过例子学 Rust:用简短的例子讲解
From、Into、TryFrom和TryInto。 Option— 标准库文档(英文):从Option里取值的所有方法,比如unwrap_or和map。