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::parse
。collect
和 parse
都是泛型方法,但编译器需要知道确切类型才能编译使用它们的代码。请注意,RustRover 没有高亮显示错误,因为我们仍在完善其类型检查功能。
可以通过多种方式修正这个问题,因为不止一个地方可以添加类型注解。我们可以指定 numbers
向量的具体类型:
let numbers: Vec = "1 5 6 3"
或者我们可以在调用 collect
时提及相同的类型:
.collect::();
最后,我们可以在不同的地方提及不同的类型:
let numbers = "1 5 6 3"
.split_whitespace()
.map(str::parse::)
.map(Result::unwrap)
.collect::();
这个错误很容易修正,指定需要的类型即可。
常见错误 #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 的启用功能。
super
或 crate
这样的特殊路径名称也可能存在问题,特别是在不同的 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 的哪一部分最麻烦?”
本博文英文原作者:Vitaly Bragilevsky
RustRover 相关阅读
⏬ 戳「阅读原文」了解更多信息
本文分享自微信公众号 – JetBrains(JetBrainsChina)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。