Rust 编译错误详解
Rust 编译器报了一堆红色的错,却看不懂它到底想要什么?你来对地方了。每个 Rust 开发者都会遇到这些错误,常常一天好几次。好消息是:反复出现的错误其实就那么几个,认识它们之后,就一点也不可怕了。
下面每一页讲一个错误码:先给出真实的报错信息,再用大白话解释它的意思,最后给出可以直接在浏览器里运行的修复方法。
最常见的错误
Section titled “最常见的错误”| 错误 | 编译器怎么说 | 用大白话说 |
|---|---|---|
| E0382 | borrow of moved value | 你把一个值交出去了,之后又想再用它 |
| E0502 | cannot borrow as mutable because it is also borrowed as immutable | 别人还在读一个值,你就去改它 |
| E0499 | cannot borrow as mutable more than once at a time | 两个地方想同时修改同一个值 |
| E0308 | mismatched types | 这里要的是一种类型,你给的却是另一种 |
| E0277 | the trait bound is not satisfied | 某个类型做不到你要求的事(打印、比较、相加……) |
| E0106 | missing lifetime specifier | Rust 不知道返回的引用能有效多久 |
| E0425 | cannot find value in this scope | 名字拼错了,或者在它所在的代码块之外使用了它 |
| E0599 | no method named … found | 这个类型上没有这个方法,或者没有导入提供它的 trait |
| E0384 | cannot assign twice to immutable variable | 你修改了一个没有声明 mut 的变量 |
| E0596 | cannot borrow as mutable, as it is not declared as mutable | 你想通过一个不是 mut 的变量或引用去修改值 |
| E0597 | borrowed value does not live long enough | 引用指向的值已经没了,引用却还在使用 |
| E0515 | cannot return reference to local variable | 你返回了一个指向局部变量的引用,而它在函数结束时就被丢弃了 |
| E0373 | closure may outlive the current function, but it borrows … | 闭包(常见于线程)借用了一个局部变量,而闭包运行时它可能已经不在了 |
| E0369 | binary operation == cannot be applied to type … |
你的类型(还)不支持 ==、<、+ 这类运算符 |
| E0432 | unresolved import | use 语句指向的 crate、模块或条目找不到 |
| E0433 | cannot find type … in this scope (older: failed to resolve) | 像 HashMap::new() 这样的路径用到了没导入或不存在的名字 |
| E0424 | self value is a keyword only available in methods with a self parameter |
你在一个不接收 self 的函数里用了 self |
| E0061 | this function takes N arguments but M arguments were supplied | 参数传多了或传少了(Rust 没有可选参数) |
| E0594 | cannot assign to p.hp, as p is not declared as mutable |
没有 mut 权限,却修改了字段或 & 后面的值 |
| E0004 | non-exhaustive patterns: X not covered |
你的 match 没有覆盖所有可能的值 |
| E0507 | cannot move out of x which is behind a shared reference |
你想拿走一个只是借来的值的所有权 |
| E0603 | function x is private |
你用了一个不是 pub 的函数、结构体或模块 |
| E0716 | temporary value dropped while borrowed | 你保留了一个临时值的引用,而这个值在这一行结束时就被扔掉了 |
| E0463 | can’t find crate for x |
某个 crate(或某个目标平台的标准库)没有安装 |
| E0368 | binary assignment operation += cannot be applied to type … |
你的类型不支持 +=、-= 这类运算符 |
| E0057 | this function takes N arguments but M were supplied (note: closure defined here) | 调用闭包时参数传多了或传少了 |
| E0282 | type annotations needed | Rust 自己推断不出类型,需要你写明 |
| E0609 | no field x on type T |
字段名拼错了,或者值被包了一层(比如在 Option 里) |
| E0593 | closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments | 这里需要一个接收元组(或别的参数个数)的闭包,你的闭包却接收了几个单独的参数 |
| E0631 | type mismatch in closure arguments | 闭包参数的类型和实际收到的不一致(常见的是 i32 和 &i32) |
| E0005 | refutable pattern in local binding | let Some(x) = … 可能匹配失败,所以普通的 let 不接受它 |
| E0615 | attempted to take value of method len on type String |
你写成了 s.len,而不是 s.len() |
| E0610 | {integer} is a primitive type and therefore doesn’t have fields |
你在数字、bool 或 char 上用了 .field |
| E0560 | struct User has no field named nmae |
结构体字面量里用了一个结构体没有的字段名 |
| E0616 | field balance of struct Account is private |
你在模块外读取了一个不是 pub 的字段 |
| E0451 | field x of struct Y is private |
你用字面量创建结构体,但它有些字段是私有的 |
| E0624 | method x is private |
你调用了一个不是 pub 的方法或关联函数 |
| E0283 | type annotations needed (cannot infer type … multiple impls satisfy this bound) | 好几种类型都说得通(比如 .collect()、.sum()),你得选一个 |
| E0284 | type annotations needed: cannot satisfy <_ as FromStr>::Err == _ |
"42".parse() 不知道要解析成哪种类型 |
| E0790 | cannot call associated function on trait without specifying the corresponding impl type |
你写了 Default::default() 或 Trait::new(),却没说是哪个类型 |
| E0559 | variant Shape::Circle has no field named r |
枚举变体的字面量用了一个变体没有的字段名 |
| E0063 | missing field email in initializer of User |
创建结构体时漏掉了一个或多个字段 |
| E0689 | can’t call method pow on ambiguous numeric type {integer} |
2.pow(10) 或 2.0.sqrt() 需要一个具体的数字类型,比如 i32 或 f64 |
| E0034 | multiple applicable items in scope | 两个 trait 给你的类型提供了同名方法,Rust 不知道该选哪个 |
| E0412 | cannot find type X in this scope (Rust 1.93+ prints E0425) |
类型名拼错了、没导入或者没声明 |
| E0428 | the name area is defined multiple times |
两个条目用了同一个名字;Rust 没有函数重载 |
| E0026 | variant Shape::Circle does not have a field named r |
模式里写了一个结构体或变体没有的字段 |
| E0027 | pattern does not mention fields id, paid |
结构体模式漏掉了字段;把它们补上,或者用 .. 结尾 |
| E0533 | expected unit struct, unit variant or constant, found struct variant Shape::Circle |
在需要普通名字的地方用了结构体变体(或函数);加上 { .. } |
| E0592 | duplicate definitions with name reset |
两个 impl 块定义了同名的方法 |
| E0255 | the name Error is defined multiple times |
use 导入的名字和你自己的类型或函数重名了 |
| E0252 | the name Result is defined multiple times |
两行 use 导入了同一个名字;用 as 给其中一个改名 |
| E0411 | cannot find type Self in this scope |
你在 impl 或 trait 之外用了 Self |
| E0405 | cannot find trait Display in this scope |
trait 名拼错了或者没导入(比如 use std::fmt::Display;) |
| E0532 | expected unit struct, unit variant or constant, found tuple variant Shape::Circle |
模式里写元组变体(或结构体)时漏了 ( ),或者在不该加的地方加了 ( ) |
| E0164 | expected tuple struct or tuple variant, found struct variant Shape::Circle |
模式在结构体变体或关联条目上用了 ( );改用 { } 或匹配守卫 |
| E0404 | expected trait, found derive macro Debug |
你写了 impl X for 或 T: X,但 X 是结构体、类型别名或没导入的派生宏 |
| E0401 | can’t use generic parameters from outer item | 内部的 fn 用了外层函数的 T 或 Self;给它自己的泛型,或者改用闭包 |
| E0107 | enum takes 2 generic arguments but 1 generic argument was supplied | Result<u32>、HashMap<String> 这类写法:< > 里的参数太少或太多 |
| E0530 | match bindings cannot shadow tuple variants | 像 Circle => 这样光秃秃的变体名(在 use Shape::* 之后)不能当作新变量 |
| E0434 | can’t capture dynamic environment in a fn item | 内部的 fn 看不到外层的局部变量;改用闭包,或者把它们作为参数传进去 |
| E0601 | main function not found in crate main |
二进制程序里没有 fn main()(漏写、拼错、放进了 mod 里,或者它其实是个库) |
| E0658 | use of unstable library feature iter_intersperse |
你在 stable 版 Rust 上用了只有 nightly 才有的 API;换成稳定的替代写法,或者升级 |
| E0614 | type u32 cannot be dereferenced |
你写了 *x,但 x 本来就是普通的值,不是引用 |
| E0435 | attempt to use a non-constant value in a constant | const、数组长度或 static 用到了 let 变量;把它改成 const,或者改用 Vec |
| E0554 | #![feature] may not be used on the stable release channel |
复制来的代码只能在 nightly 上用;删掉这个属性改用稳定的 API,或者有意识地使用 nightly |
怎么读懂 Rust 的报错
Section titled “怎么读懂 Rust 的报错”Rust 的错误信息以友好著称,但信息量也很大。来看一个真实的例子:
fn main() { let name = String::from("Ferris"); let greeting = name; println!("{name}");}error[E0382]: borrow of moved value: `name` --> src/main.rs:4:16 |2 | let name = String::from("Ferris"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait3 | let greeting = name; | ---- value moved here4 | println!("{name}"); | ^^^^ value borrowed here after move |help: consider cloning the value if the performance cost is acceptable |3 | let greeting = name.clone(); | ++++++++按这个顺序读:
- 第一行是摘要:错误码(
E0382)加一句话的描述。 - 箭头那一行(
--> src/main.rs:4:16)告诉你出错的文件、行号和列号。 ^^^^标记指向出错的确切位置。----标记讲的是前因后果。这个例子里它们在说:值在第 2 行创建,在第 3 行被移动,然后在第 4 行被使用。help:是编译器建议的修复方法,常常直接给出要加的代码(++++标记的是新加的字符)。它不一定是最好的修复,但是个很好的起点。
从第一个错误开始读。一个错误可能引发一连串后续错误,修好第一个,后面的往往就跟着消失了。
-
让编译器多讲一点。 每个错误码都内置了一份更长的解释。在终端里运行:
Terminal window rustc --explain E0382你会看到一段简短的说明、一个出错的例子和一个修好的例子(说明是英文的)。
-
经常检查。
cargo check不用构建整个程序就能找出错误,所以比cargo build快得多。每写几行就运行一次,你一次只会遇到一个错误,而不是二十个。参见 Cargo 与 crate。 -
让编辑器在你打字时就显示错误。 装上 rust-analyzer 扩展后,错误会直接显示在代码里,附带同样的 help 文字。参见 Rust 工具与环境配置。
为什么 Rust 的报错这么多
Section titled “为什么 Rust 的报错这么多”这些错误大多存在的原因是:别的语言要到运行时才会发现(甚至永远发现不了)的问题,Rust 在程序运行之前就检查了:使用已经释放的内存、两个线程同时修改同一份数据、忘了检查 null。你每修好一个错误,就少了一个会跑到用户那里的 bug。
所以很多人说:在 Rust 里,仗都在编译时打完了,之后程序就能顺顺当当地跑起来。最常见的那些错误背后的规则,在所有权、借用和生命周期这几页里有详细讲解。
小测验
1. Rust 报了好几个错误。应该先修哪一个?
2. rustc --explain E0382 有什么作用?
3. 在错误信息里,^^^^ 标记表示什么?
得分: 0 / 3
- Rust Compiler Error Index(英文):所有错误码的官方列表,每个都附有出错的例子和修好的例子。
- 认识所有权 — Rust 程序设计语言:最常见的借用和移动错误背后的规则。
- 错误处理 — Rust 程序设计语言:讲的不是编译错误,而是怎么在你自己的程序里处理错误(
Result和panic!)。 - What is rustc? — The rustc Book(英文):编译器是怎么工作的,包括它的命令行参数和 lint 级别。