RustRover 中最常见的 Rust 编译器错误:第 2 部分
在本博客系列的第 1 部分中,我们根据 RustRover 的使用数据深入分析了 10 大最常见 Rust 编译器错误中的后 5 个。 在本部分中,我们将探讨最常发生的前 5 大错误,并对 Rust 编程语言中最容易使开发者与编译器产生“冲突”的方面进行一些更总体的观察。
常见错误 #5:E0433(使用了未声明的 crate、模块或类型)
这个错误类似于上一部分中谈到的 E0432(import 未解析)。 唯一的区别是,具有问题组件的路径直接在名称中使用,不带 use
子句。 17.5% 的 RustRover 用户遇到过这个错误。 例如,在下面的代码中,编译器无法理解我们真正的意图(crate 还是模块)并报错:
这个示例展示了 Rust 编译器备受好评的错误消息传递。 即使不阅读消息,修正也显而易见:验证名称,根据需要将所需的依赖项添加到 Cargo.toml,或者引入适当的 import。
RustRover 也可以发挥作用, 让我们选择导入的名称。
编写代码时使用代码补全就可以完全避免这种错误。
常见错误 #4:E0425(使用了未解析的名称)
接下来是另一个“未解析”的错误。 20.5% 的 RustRover 用户遇到过这种情况:使用了未解析的名称,它在语法上不是 crate 或模块名称。 发生这种错误时会弹出:
当然,实际发生时,情况可能不会像上图中那样明朗。
可惜,即使是了不起的 Rust 编译器也难以应对这种错误。 可以采取的方式是:修订名称、提供定义或引入适当的 import。
常见错误 #3:E0599(方法用于未实现该方法的类型)
剧透预警:从现在起都将是类型检查错误! 没错,前三名的错误都与不正确的类型使用相关。 这里的错误是,27.5% 的 RustRover 用户曾试图在未实现方法的类型的值调用上该方法。 来看下面的例子:
fn main() { let numbers: Vec = vec![4, 11, 15, 12]; let sum: usize = numbers.sum(); println!("Answer = {sum}"); }
看起来没问题,只是没有作用于 Vec
的 sum
方法。 具体错误消息如下:
error[E0599]: `Vec` is not an iterator
这里的修正是首先调用 iter
方法,然后在生成的迭代器上使用 sum
。
另外,RustRover 有一项功能可以防止这种错误, 即链式补全。 要查看实际益处,请查看以下示例:
当补全建议被采纳时,iter
和 sum
方法都会添加到调用链中,立即修正代码。
通常,代码补全可以帮助您避免 E0599 消息。 如果您没有看到所需方法调用的建议,那么真正的问题可能是被调用方类型不正确。
这个错误还有一个方面值得一提:在许多上下文中,您可以控制自己的类型声明。 一种选项是首先调用一个方法,然后按 Alt-Enter (⌥Option–Return) 并执行建议的操作让 RustRover 搭建其实现:
操作的结果是,RustRover 生成方法实现模板,同时考虑到有关类型的所有上下文信息:
impl Info { pub(crate) fn summary(&self) -> String { todo!() } }
运行这段代码会导致运行时错误,但至少编译器不再报错。
常见错误 #2:E0308(预期类型与接收的类型不匹配)
设想一下:在需要类型 B
的值的上下文中使用了类型 A
的表达式。 这里的“上下文”可以指调用点处的函数形参、变量声明、控制流语句/表达式等。 如果您遇到过这个问题,那么您也是 30% 的 RustRover 用户中的一员。
理论上,对于这种情况我们有两种选择:调整值或调整上下文。 调整值可能涉及转换类型、引用/解引用、在值上调用转换方法或将其替换为其他内容。 调整上下文并不总是可行,有时我们可以更改预期类型来匹配接收的类型。
RustRover 通过提供快速修复选项支持两种操作模式,例如:
注意列表中的第二个建议:在这种情况下,RustRover 提出了适合编译器的相当复杂的值转换。 遵循第一个建议将修正上下文,即函数定义。
常见错误 #1:E0277(尝试使用一个类型,但该类型未在需要特征的位置实现特征)
我们终于到榜首了! RustRover 中最常见的 Rust 编译器错误是 E0277, 32% 的 RustRover 用户经历过这种以类型和特征为主题的开发者噩梦。 像往常一样,官方说明很好地提供了示例并解释了可能的修正。 我们来关注 RustRover 的行为。
对于这种错误,我最喜欢的 RustRover 功能如下所示:
编译器报错:
error[E0277]: `Info` doesn't implement `std::fmt::Display`
当然,实现 Display
特征是其中一种选择,RustRover 也很乐意搭建实现。 但在这种情况下,我更倾向于采用第一个建议,其中涉及两个同时进行的步骤:
- 派生
Debug
特征的实现。 - 更改格式字符串以调用
Debug
特征的格式化功能。
然而,在许多其他情况下,RustRover 无法自行发现和高亮显示此错误。 如前一部分所述,它的类型检查功能还没有那么强大,但我们正在改进。 作为最常见的错误消息,E0277 当然在我们的关注范围之内。
Rust 错误的总体观察
我们来看概况。 上周,我们向 X(以前称为 Twitter)社区询问 Rust 编程语言的什么方面是代码错误的主要来源。 以下是我们得到的回复:
为了将社区回复与我们从数据中观察到的结果进行比较,我们研究了 25 个最常见的 Rust 编译器错误,并将其大致分为五类:
- 类型和特征
- 所有权和生命周期
- 宏
- 未解析的名称或不存在的元素
- 其他
结果如下(按错误代码数字从小到大的顺序排列):
错误代码 | 描述 | 类别 |
E0061 | 调用函数时传递的实参数量无效 | 其他 |
E0063 | 未提供结构或类结构枚举变体的字段。 | 未解析/不存在 |
E0106 | 此错误表明类型缺少生命周期。 | 所有权和生命周期 |
E0107 | 提供的泛型实参数量不正确。 | 类型和特征 |
E0277 | 尝试使用一个类型,但该类型未在需要特征的位置实现特征 | 类型和特征 |
E0282 | 编译器无法推断类型并要求类型注解。 | 类型和特征 |
E0283 | 由于缺乏信息无法明确选择实现。 | 类型和特征 |
E0308 | 预期类型与接收的类型不匹配。 | 类型和特征 |
E0369 | 尝试对不支持的类型进行二元运算。 | 类型和特征 |
E0382 | 内容移动到其他位置后变量才被使用。 | 所有权和生命周期 |
E0412 | 使用的类型名称不在作用域内。 | 未解析/不存在 |
E0423 | 标识符像函数名称一样使用,或者需要一个值并且标识符存在,但它属于不同的命名空间。 | 未解析/不存在 |
E0425 | 使用了未解析的名称。 | 未解析/不存在 |
E0432 | import 未解析。 | 未解析/不存在 |
E0433 | 使用了未声明的 crate、模块或类型。 | 未解析/不存在 |
E0502 | 已被借用为不可变对象的变量被借用为可变对象。 | 所有权和生命周期 |
E0507 | 借用的值被移出。 | 所有权和生命周期 |
E0515 | 返回了对局部变量的引用。 | 所有权和生命周期 |
E0596 | 发生此错误是因为您尝试以可变方式借用不可变变量。 | 所有权和生命周期 |
E0597 | 发生此错误是因为值在借用期间被删除。 | 所有权和生命周期 |
E0599 | 发生此错误是因为方法用于未实现该方法的类型。 | 类型和特征 |
E0609 | 尝试访问结构中不存在的字段。 | 未解析/不存在 |
E0614 | 尝试解引用无法解引用的变量。 | 其他 |
E0658 | 使用了不稳定的函数。 | 其他 |
E0716 | 临时值正被删除,而借用仍在有效使用中。 | 所有权和生命周期 |
很遗憾,我们掌握的数据并没有太多关于宏的信息。 我们既无法可靠地检测宏展开问题,也无法识别源自成功展开的宏的其他错误。 也许这表明我们应该对收集的数据进行更精细的分类。
不考虑宏,前 25 大常见错误中似乎没有明显的赢家,这说明我们平等地喜爱 Rust 的每一面:
- 类型和特征 – 7 个错误
- 所有权和生命周期 – 8 个错误
- 未解析的名称或不存在的元素 – 7 个错误
- 其他 – 3 个错误
概要
在本系列的这一部分中,我们研究了 RustRover 中的五大最常见 Rust 编译器错误。 其中三种错误与类型和特征有关,表明这一错误类别对于正确编写 Rust 代码颇为关键。 我们观察了 25 个最常见错误的总体数据,意识到 Rust 的其他方面也会导致我们经常遇到的错误。
本博文英文原作者: