跳转到内容

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()。

每条 E0308 报错都是同一个样子:expected A, found B(需要 A,找到的是 B)。

  • expected(需要的)是 Rust 在这个位置要求的类型。它来自别处:类型注解、函数的参数类型或返回类型、结构体字段,或者 if/match 的另一个分支。
  • found(找到的)是你实际写在这里的值的类型。

在这个例子里,name 字段声明为 String,所以 Rust 需要的就是 String。但 "Ferris" 是一个字符串字面量,它的类型是 &str。两者有关系,但不是同一个类型。

也要留意 help: 那几行。遇到 E0308 时,编译器常常会直接给出准确的修复方法,比如上面的 .to_string()。

Rust 从不悄悄地在类型之间转换。在有些语言里,数字会悄悄变成字符串,或者一个大数会被悄悄截断,好塞进更小的类型。这些看不见的转换会造成真实的 bug:数据丢失、结果出错,还有在离真正错误很远的地方发生的崩溃。

所以 Rust 让每一次转换都在代码里看得见。要多敲几个字,但以后读代码时,你能清楚地看到值在哪里换了类型,以及这一步会不会失败。类型本身的介绍见数据类型。

下面每个修复都先给出报错的样子,你可以直接跳到和你的报错对应的那一个。

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

如果你看到 found (),就去检查函数的最后一行。在 Rust 里,代码块中最后一个不带分号的表达式就是它的返回值。加上 ; 会把它变成一条语句,于是函数返回的是 ()(表示“什么都没有”的类型)。编译器也提示了:函数体没有末尾表达式,所以隐式返回了 ();删掉这个分号就能返回这个值。把分号删掉:

fn total_price(price: u32, quantity: u32) -> u32 {
price * quantity // 没有分号:返回这个值
}
fn main() {
println!("总价:{}", total_price(250, 3));
}

输出:

总价:750
▶ 动手试一试

if 代码块和 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"]
▶ 动手试一试
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 和 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} 名玩家");
}

输出:

平均分:81
3 名玩家
▶ 动手试一试

很多时候,最简单的修复是改一下类型注解: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 this

Option<usize> 不是 usize。它的意思是“也许是一个 usize,也许什么都没有”。这种不匹配有两个方向:

  • expected T, found Option<T>:你得处理“什么都没有”的情况,用 match、if let 或 unwrap_or(默认值)。
  • expected Option<T>, found T:你的函数返回 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(); 会报“expected u32, found &str”。文本不是数字。修复: 先解析它:let age: u32 = input.trim().parse().unwrap();(或者处理错误,见 Result)。
  • 把字面量 push 进 Vec<String>。 tags.push("rust") 会报“expected String, found &str”。修复: 写成 tags.push("rust".to_string());如果里面全是字面量,也可以把它改成 Vec<&str>。
  • 混用不同数字类型做运算。 total 是 i64、n 是 i32 时,total + n 会报 E0308(旁边常常还有 E0277)。修复: 先转换其中一边:total + i64::from(n)。

小测验

1. 函数声明了 -> i32,报错却说“expected i32, found ()”。最可能的原因是什么?

2. 一个函数接收 &str,而你有 let name = String::from("Ana");。怎么把它传进去?

3. 哪种转换永远不会悄悄丢失数据?

得分: 0 / 3