RustRover 中最常见的 Rust 编译器错误:第 2 部分

Read this post in other languages:

本博客系列的第 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}");
}

看起来没问题,只是没有作用于 Vecsum 方法。 具体错误消息如下:

error[E0599]: `Vec` is not an iterator

这里的修正是首先调用 iter 方法,然后在生成的迭代器上使用 sum。 

另外,RustRover 有一项功能可以防止这种错误, 即链式补全。 要查看实际益处,请查看以下示例:

当补全建议被采纳时,itersum 方法都会添加到调用链中,立即修正代码。

通常,代码补全可以帮助您避免 E0599 消息。 如果您没有看到所需方法调用的建议,那么真正的问题可能是被调用方类型不正确。

这个错误还有一个方面值得一提:在许多上下文中,您可以控制自己的类型声明。 一种选项是首先调用一个方法,然后按 Alt-Enter (⌥OptionReturn) 并执行建议的操作让 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 的其他方面也会导致我们经常遇到的错误。

 

本博文英文原作者:

Sue

Vitaly Bragilevsky

image description

Discover more