目录
第一章:环境搭建

啊,第一步。在空白画布上全新启程的感觉不是很美妙吗?然后选定那块你将用来建造整个宫殿的基石?
遗憾的是,当你构建计算机程序时,第一步可能会变得......复杂且令人沮丧。你必须确保开发环境已针对所使用的编程语言进行配置,还要弄清楚如何在该环境中编译和运行程序。
幸运的是,Rust 自带的开发环境已为我们完成了大部分工作,因此除了文本编辑器外你几乎不需要其他配置。本教程虽然是在 Mac 系统上编写的,但同样适用于 Windows 和 Linux 系统。
我们将使用 rustup
来安装 Rust,该工具可以管理已安装的 Rust 版本及相关工具链。
Rust 附带了一本极好的免费书籍:《Rust 编程语言》。每当书中解释得比我更透彻时,我会经常链接到这本书以获取更深入的知识。要安装 rustup
,只需按照第一章第一节的安装指南操作即可。在撰写本文时,Mac 上的安装流程如下:打开终端(通过 Spotlight 搜索"终端"找到),然后输入以下命令:
arduino
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
安装程序会显示若干选项,按 Enter
即可选择默认配置。安装结束时会出现如下鼓舞人心的提示:
csharp
Rust is installed now. Great!
重启终端后,输入以下命令:
css
rustc --version
您应该能看到已安装版本的相关信息。如果未显示,请查阅安装指南进行故障排查。
顺便说一句,本教程会频繁使用终端,所以请保持终端开启状态。
The main()
function
创建一个名为 hecto.rs
的新文件,并在其中编写一个 main()
函数( hecto
是我们正在构建的文本编辑器的名称)。
rust
fn main(){
return;
}
在 Rust 中,所有可执行代码都必须放在函数内部。Rust 中的 main()
函数具有特殊意义,它是程序运行时默认的起始点。当从 main()
函数返回时,程序将退出并将控制权交还给操作系统。
Rust 是一门编译型语言。这意味着我们需要通过 Rust 编译器将程序转换为可执行文件,然后就可以像在命令行中运行其他程序一样运行这个可执行文件。
要编译 hecto.rs
,请在终端中运行 rustc hecto.rs
。如果没有错误发生,这将生成一个名为 hecto
的可执行文件。要运行 hecto
,输入 ./hecto
并按 Enter
。程序不会输出任何内容。
使用 cargo
进行编译
理论上, rustc
就是构建 hecto
所需的全部。实际上,Rust 附带了一些便利工具,能让开发更轻松,并且如你稍后将看到的,也更安全。
Rust 附带了一个名为 cargo
的程序。如果你熟悉 JavaScript
生态系统,那么 cargo
可以最贴切地描述为 Rust 版的 npm
。它帮助你管理依赖项、编译代码以及其他事项。正确安装 Rust 后,你可以输入以下命令来调用 cargo:
css
cargo --version
我们将使用这个程序来开始 hecto 项目。通过输入 rm hecto.rs hecto
,删除你之前的 hecto.rs
和可执行文件 hecto
。
在您希望 hecto
诞生的目录中,输入以下命令:
css
cargo init hecto --vcs none
使用 --vcs none
参数可确保您初始化 hecto
时不启用 git
支持。若要在本项目中使用 git
,请省略此标志(但这样您需要先安装 git
)
cargo
会通过以下提示告知您初始化成功:
java
Created binary (application) package
它创建了一个名为 hecto
的新文件夹,其中包含若干文件。稍后我们将逐一查看这些文件,最值得关注的是位于 src
文件夹中名为 main.rs
的文件。
让我们来看看这个文件。当你打开它时,会发现里面已经包含了一个 main()
。仅从内容就能推断出,这个程序遵循古老的传统------打印出"Hello, World!"然后退出。
要使用 cargo
编译这个程序,请确保你位于 hecto
文件夹中(在 cargo init hecto
后运行 cd hecto
),然后在终端运行 cargo build
。与 rustc
不同, cargo
不会保持静默,它会输出类似这样的内容:
scss
Compiling hecto v0.1.0 (/Users/pflenker/repositories/hecto)
Finished dev [unoptimized + debuginfo] target(s) in 12.36s
运行此命令会添加更多文件(我们稍后会查看这些文件),并将一个名为 hecto
的可执行文件放入 target/debug
文件夹中。让我们执行 ./target/debug/hecto
来确认这个程序确实会输出 Hello, World!
然后退出。
理解 cargo
的额外文件(及功能)
现在我们的 hecto
目录下包含了许多内容(其中有些是隐藏文件,具体内容可能因机器而异):
- 一个名为
src
的文件夹,内含main.rs
。 - 两个分别名为
Cargo.toml
和Cargo.lock
的文件 - 一个名为
target
的文件夹,里面有一些隐藏文件,还有一个名为debug
的文件夹。该文件夹包含更多文件和子文件夹,以及我们的可执行文件hecto
。这些额外的东西有什么用呢?让我们来了解一下。
src
文件夹
src
文件夹将存放我们所有的源代码文件。目前只有一个文件,很快会添加更多。
Cargo.toml
和 Cargo.lock
这个文件遵循名为 TOML 的配置格式,用于向编译器传递待编译代码的相关信息。我们将在后续段落详细查看默认配置文件的内容。
cargo
还为我们处理依赖管理,而 Cargo.toml
会帮我们保存这些依赖项。 Cargo.lock
是依赖管理的一部分,它能确保不同环境中的依赖项保持一致。现在不必担心这个问题------我们将在下一章深入探讨。
Build Targets
让我们来看看 target
及其内容。
cargo
支持多种所谓的构建目标。默认使用的是 debug
,这意味着最终的可执行文件主要面向我们开发者,而非实际的终端用户。
另一个有效的目标可能是 release
,这正好相反:它是面向客户的产品,而不仅仅是开发者工具。
要构建发布版本,请运行:
arduino
cargo build --release
完成后,你会注意到 target
文件夹中出现了另一个恰如其名称为 release
的文件夹。
等等,"可执行文件面向特定人群"是什么意思?它不就只是打印"Hello World"吗,不管谁使用都一样?
确实如此。
首先,Rust 编译器会尽量为开发者和终端用户提供便利,因此它对 debug
和 release
构建采取了不同的处理方式。稍后我们会遇到一个具体例子,但存在几类编程错误时,Rust 会认定你绝对、绝对不是有意为之。如果 debug
构建遇到这类错误,程序会直接崩溃------这是 Rust 能采取的最极端手段,以此表明你的错误有多严重。但 Rust 其实具备从这类错误中恢复的能力,可以让程序继续运行(尽管可能产生错误结果)。考虑到对终端用户而言,错误结果固然糟糕,但程序崩溃更难以接受,因此同样的错误在 release
构建中就不会导致崩溃。
其次,编译器可以在后台执行各种优化,使结果运行得更快。但这些优化需要时间,会拖慢编译速度。谁需要频繁编译?开发者。谁从不编译?用户。因此, debug
构建版本会禁用优化以优先考虑开发速度,而 release
构建版本则启用优化以优先考虑执行速度。
最后,这个可执行文件不仅仅包含"Hello, World"代码,Rust 还会在 debug
构建中添加有助于更快调试问题的信息。这些信息对客户来说并不需要,因此可以从发布版本中排除。
cargo
在编译过程中也向我们传达了这一点, debug
版本的最终行如下所示:
scss
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
这告诉我们该构建版本未经优化且包含调试信息。
而 release
构建版本在编译结束时显示:
scss
Finished release [optimized] target(s) in 0.04s
这表明该版本不包含调试信息,且是优化后的构建版本。
让我们仔细看看调试信息,但别动我们崭新且已深爱的文本编辑器代码。直接前往 Rust Playground,那里已经贴心地内置了我们的"hello world"函数。在左上角你会看到几个按钮:先是 Debug,接着是 Stable,然后是 ...
。点击这三个点并启用 backtrace
。
接着按以下方式修改代码沙盒中的内容:
rust
fn main() {
panic!("Hello, World");
}
panic!
会直接引发崩溃,简单明了。点击"运行"执行程序,然后切换至"调试"模式,将其改为"发布"模式后再次运行。
你会注意到 release
构建生成了以下输出:
arduino
Running `target/release/playground`
thread 'main' panicked at src/main.rs:2:4:
Hello, World
stack backtrace:
0: rust_begin_unwind
1: core::panicking::panic_fmt
2: playground::main
debug
的输出看起来像这样:
bash
Running `target/debug/playground`
thread 'main' panicked at src/main.rs:2:4:
Hello, World
stack backtrace:
0: rust_begin_unwind
at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/std/src/panicking.rs:647:5
1: core::panicking::panic_fmt
at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/panicking.rs:72:14
2: playground::main
at ./src/main.rs:2:4
3: core::ops::function::FnOnce::call_once
at /rustc/7cf61ebde7b22796c69757901dd346d0fe70bd97/library/core/src/ops/function.rs:250:5
我们尚未详细讨论堆栈跟踪,但即使不了解相关知识,你也能立即看出调试输出比发布版本包含更多信息。理解调用栈(即函数调用其他函数的顺序,直至某个函数崩溃)会非常有用。
构建产物
这样我们就解释了文件夹的用途,但那些不是 hecto
的内容又是什么呢?让我们深入研究一下来增进理解。
回到终端,依次运行以下命令:
cargo clean
cargo build
cargo build
(是的,我们运行了两次 cargo build
)
第一条命令会清理 target
目录。第二条命令和之前一样执行。但在第三条命令执行时, cargo
不再像之前那样输出以 Compiling
开头的行。这是因为 cargo
能识别出当前版本的 main.rs
已经完成编译。如果自上次编译后 main.rs
未被修改, cargo
就不会重复执行编译过程。若 main.rs
发生变动, cargo
则会重新编译 main.rs
。随着代码库规模扩大,这个机制会愈发实用------当你仅修改某个组件的源代码时,大多数组件无需被反复重新编译。
target
目录中所有(好吧:大部分)额外文件都是构建产物,它们能让 cargo
在后续编译时更快完成。更多详情请参阅 cargo's
文档。
编译与运行
由于编译和运行程序是非常常见的需求, cargo
通过 cargo run
命令将这两个步骤合二为一。
尝试将 main.rs
中的返回值更改为 Hello, World
以外的字符串。然后运行 cargo run
,你应该能看到它编译。检查结果是否显示你修改后的字符串。接着将其改回 Hello, World
,重新编译,确保程序恢复为返回 Hello, World
的状态。
代码审查
让我们一起来看这段代码。
点击上方链接,您将看到一个带注释的提交记录。它会展示从上一步到当前步骤之间的所有代码变更,并在相关代码行旁直接显示注释说明。
在这种情况下,我描述的是该提交中 Cargo.toml
的内容。
采用这种提交方式工作有很多优点:
- 你可以直接在相关代码上看到解释,更容易理解代码变更。
- 你可以直接提问(需要 GitHub 账号)或对不理解的代码片段发表评论
刚开始你可能需要在本教程和 GitHub 之间来回切换,这会有些累人。不过等我们掌握了基础知识后,我将不再提前展示我的操作让你重复------而是先告诉你任务要求,之后再展示我的解决方案,这样你就不需要频繁切换 GitHub 了。
在本教程中,我会提供大量延伸资料的链接。这里说明一下链接的使用规范:理解教程所需的关键信息会直接呈现在 GitHub 提交记录或正文内容中。其他未明确标注为 GitHub 操作步骤的链接均属于可选内容,不感兴趣的话可以跳过。
总结与展望
在本章中,我们安装了 Rust,初始化了一个基础项目并熟悉了相关操作。现在我们已经掌握了手动编译和使用 cargo
编译代码的方法,并对 cargo
及其生成的文件有了初步认识。最重要的是,我们能够构建项目并在需要时清理生成文件。我们还认识了 Rust Playground 工具,当我们需要先独立测试新功能再投入实际使用时,这个工具肯定会派上用场。
在第二章中,我们将构建一个能读取用户输入、在屏幕上显示内容,并在按下 q
时退出的程序。仅这个简单的程序就包含大量值得初学者掌握的知识点。
- 终有一天,你将能用
hecto
来编辑hecto
,甚至不再需要文本编辑器。 - Rust 支持异步编程(本教程不涉及此内容)。在这种情况下,
main
完全有可能已执行完毕却尚未将控制权交还给操作系统。 - 不知你是否有同感,但创建一个真实可用的、成熟的应用程序对我来说始终是充满魔力的时刻。
- 根据你的知识背景,可能觉得这个问题很奇怪,毕竟构建变体的概念如今已非常普遍。但过去情况并非如此。