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

Read this post in other languages:

Rust 编译器相当挑剔, 如果它对输入的源代码不满意,可能会发出 400 多种不同的错误(而且每个月都在增加!)。 有些错误极其罕见,另一些则每天都困绕着 Rust 开发者。 在这个博文系列中,我们将介绍开发者在 RustRover(JetBrains 推出的专属 Rust IDE)中遇到的最常见 Rust 编译器错误消息,并说明如何避免这些错误。 首先,我们来看一下“最常见错误”实际上指的是什么。

从 RustRover 的使用数据中识别最常见的错误

任何 RustRover 用户都可以选择向 JetBrains 发送其匿名使用数据。 通过分析这些数据,我们可以观察各种用户模式并深入探究如何改进 IDE。 当然,我们非常重视您的隐私,因此 IDE 收集的信息非常有限。 例如,数据中的任何内容都无法追溯到用户。 但匿名数据仍然可以告诉我们 IDE 的一般使用情况,例如最常生成的错误消息类型。

当选择加入的用户通过 IDE 启动 Cargo Build 命令(例如,通过触发需要构建项目的运行配置)并且 Rust 编译器发出错误时,我们就会记录错误代码。 这不包括用户编写代码时出现的所有代码问题,仅包括在用户构建项目后仍然存在的问题。 中间错误通常可以通过 IDE 的检查和快速修复处理。 用户向我们发送的使用数据越多、使用 RustRover 的频率越高,我们就越能了解他们的体验,也就越能改进 IDE 的代码辅助功能。 因此,感谢所有加入的用户通过其使用数据帮助我们改进 RustRover!

我们从运行 RustRover 的用户处收集错误代码,并根据遇到错误的用户数量对其排名。 本系列的这一部分将讨论第 10 到 6 名最常见错误,下一部分将揭示前 5 名最常见错误。 我们将研究这些错误背后的原因,探索简单示例,并探讨潜在修正方式。

常见错误 #10:E0412(使用的类型名称不在作用域内)

Rust 在类型声明点和类型名称用法之间保持严格区分。 每个类型名称(包括泛型类型)都必须在某处声明,并且在其使用作用域内可用。 如果编译器遇到类型名称用法但没有关于其声明点的任何信息,则会发出 E0412。 大约 12% 的 RustRover 用户遇到过此错误。

假设您输入了 i42 而不是 i32, RustRover 会发现问题并高亮显示未知类型名称。 编译器提供了更多详细信息并建议修正,点击编译器输出中的 Apply fix(应用修正)按钮即可轻松应用:

其他导致 E0412 的情况包括:

  • 忘记声明类型。
  • 将类型导入到当前作用域。
  • 引入泛型类型名称而使编译器无法访问类型。

要修正这一问题,可以提供类型声明(声明结构或正确引入泛型类型名称)或将类型引入作用域(通过 use 子句)。 官方 E0412 错误说明给出了此错误的更多示例。

常见错误 #9:E0061(调用函数时传递的实参数量无效

虽然 RustRover 可以感知到这个错误并提供一系列修正,但 13% 的 RustRover 用户会在构建项目之前忽略这个错误。

错误本身不需要过多解释:我们有一个函数,要么在当前作用域中声明,要么从其他地方导入,而调用点给出的实参太少或太多。 我们来看一个示例,并比较 RustRover 的建议和 Rust 编译器的建议:

这个示例展示了一个常见场景:打开文件。 如果我们习惯了使用其他编程语言编码,就可能提供第二个实参,忘记在 Rust 中这个方法只需要一个实参。 RustRover 和 Rust 编译器都建议移除第二个实参。 很好,我们不需要构建项目就可以从 IDE 获得实用建议。 注意代码中的红色波浪线,它们通常都有意义!

如果调用的函数是在我们自己的代码中定义的,情况就更有趣了。 假设我们继续添加到相同的代码示例:

在这种情况下,RustRover 建议向函数添加形参作为第一个替代方案,这应该没有问题。 但 Rust 编译器则坚持将其移除。 这种差异有其原因。 编译器的工作是确保程序正确,为此,最简单的方式就是消除调用点的额外实参。 然而,IDE 的作用是让您更接近您想要达成的目标。 如果您是为自己的函数输入了这个实参,那么您很有可能是有意为之,因此 RustRover 会尝试帮助您完成工作。

常见错误 #8:E0282(编译器无法推断类型并要求类型注解

有时编译器会不知所措,无法确定变量所需的类型,只能建议手动添加类型注解。 如果您遇到过这个错误,您并不孤单,13.5% 的 RustRover 用户也遇到过。

E0282 这样的错误主要源于泛型性。 许多库函数都采用泛型类型形参,但编译器必须将这些形参实例化为具体类型,因而陷入困惑。 请查看以下示例:

我们想要将字符串中的数字收集到容器中。 然而,编译器不能确定它们是什么类型的数字或什么类型的容器。

编译器建议首先指定容器类型。 但是,如果应用此修正,我们将再次遇到相同类型的错误,涉及 str::parsecollectparse 都是泛型方法,但编译器需要知道确切类型才能编译使用它们的代码。 请注意,RustRover 没有高亮显示错误,因为我们仍在完善其类型检查功能。

可以通过多种方式修正这个问题,因为不止一个地方可以添加类型注解。 我们可以指定 numbers 向量的具体类型:

let numbers: Vec = "1  5     6   3"

或者我们可以在调用 collect 时提及相同的类型:

   .collect::<Vec>();

最后,我们可以在不同的地方提及不同的类型:

let numbers = "1  5     6   3"
   .split_whitespace()
   .map(str::parse::)
   .map(Result::unwrap)
   .collect::<Vec>();

这个错误很容易修正,指定需要的类型即可。

常见错误 #7:E0432(import 未解析

RustRover 提供了大量自动补全功能。 例如,我们首先在代码中引入正则表达式:

如果选择第一个建议,除了补全本身,还会发生两件事:

  • regex crate 的依赖将添加到您的 Cargo.toml 中。
  • use regex::Regex; 子句将添加到文件顶部。

添加这样的 use 子句时,import 会自动正确写入。 但有时您需要手动编写 import,这时就可能出现 E0432 错误。15.5% 的 RustRover 用户会不时遇到这种情况,最有可能是因为他们拼错了 crate 或模块名称,尝试导入不存在的内容,或者从某处复制粘贴后将错误的 use 子句带入代码。 第一个建议始终是检查依赖项和名称。

有时 RustRover 可以帮助防止此错误。 如果知道我们尝试导入的 crate,它可以在您从外部源粘贴代码时建议添加依赖项,或者通过以下快速修复提供支持:

将相应依赖项添加到 Cargo.toml 可以立即修正此错误。 在 crate 可用后,对 use 子句中的其他路径组件使用自动补全能够避免出现更多名称问题。 另请注意,某些名称的可用性可能取决于 crate 的启用功能。

supercrate 这样的特殊路径名称也可能存在问题,特别是在不同的 Rust 版本中要以不同的方式处理。 请参阅官方说明了解详情。

常见错误 #6:E0382(内容移动到其他位置后变量才被使用

接下来是所有权问题, 17% 的 RustRover 用户遇到过这个错误。 官方说明相当详细,并提供了许多示例。 可惜,RustRover 在这里没有太大帮助。 如果禁用外部 linter,RustRover 的内部机制不会发现以下代码有任何问题:

fn main() {
   let vec = vec![1, 2, 3, 4, 5];
   let mut sum = 0;
   for v in vec {
       sum += v;
   }
   println!("Sum of {vec:?} elements is {sum}");
}

这段看似无辜的代码在其他几种编程语言中完全合法。 我们有一个向量,想要计算其元素的总和。 例如,假设我们使用 C 语言,不知道迭代器的函数式编程技巧,我们需要编写传统的 for 循环。 完成所有求和后,我们就可以输出向量和计算结果。 右侧?

在 Rust 中不行,因为它有所有权规则。 

问题在于,for 循环中的数据源扩展到具有整个向量的所有权的 into_iter() 调用。 因此,尝试访问 println! 中向量的元素时,编译器会表示它已被移动。

修正很简单,并且由编译器建议:迭代 &vec,避免将其移入循环,而应改为借用

一般来说,建议始终跟踪值所有权。 移动值和借用值是 Rust 的基本概念, 理解它们是每个学习者的首要任务。

更新一览

在博文系列的第一部分中,我们根据 RustRover 中的使用数据定义了最常见的 Rust 编译器错误,并讨论了第 10 到第 6 名的错误。 在下一部分中,我们将探索最常见的 5 个错误,并尝试回答每个 Rust 开发者都会考虑的问题:“Rust 的哪一部分最麻烦?”

 

本博文英文原作者:

Sue

Vitaly Bragilevsky

image description

Discover more