跳到主要内容

Rust Toolchain

工具链/版本管理器Rust 编译器代码格式化工具代码检查工具包管理器包注册中心
rustuprustcrustfmtclippycargocrates.io

一、简单介绍 💡

  • rustup:Rust 工具链的安装和更新工具。这个工具用于在发布新版本的 Rust 时安装和更新 rustccargo。此外,rustup 还可以下载标准库的文档。你可以同时安装多个版本的 Rust,并使用 rustup 在它们之间切换。

  • rustc:Rust 编译器,用于将 .rs 文件转换为二进制文件或其他中间格式。

  • rustfmt:用于根据样式指南格式化 Rust 代码的工具。它确保 Rust 代码的一致性并符合社区标准。

  • clippy:一组用于捕捉常见错误并改进 Rust 代码的 lint 集合。它提供惯用的 Rust 使用建议,并帮助维护代码质量。

  • cargo:Rust 的依赖管理器和构建工具。Cargo 可以下载依赖项(通常托管在 https://crates.io 上),并在构建项目时将这些依赖项传递给 rustc。Cargo 还内置了测试运行器,用于执行单元测试。

  • crates.io:Rust 的官方包注册表,开发者可以在这里发布、发现和管理 Rust 库(crates)。它是 Rust 包和依赖项的主要来源。

二、Editions 📚

Rust 版本是一种结构化的方法,用于引入新功能和改进,同时保持向后兼容性。这确保了用旧版本编写的代码仍可以使用较新的编译器进行编译。

Rust 版本每三年发布一次,使得在不破坏现有代码的情况下可以引入重大更改。

到目前为止,Rust 已经发布了三个版本:2015 年、2018 年和 2021 年。每个版本都引入了新的语法、功能和改进。

版本在 Cargo.toml 文件的 [package] 部分指定,指导 Rust 编译器应用哪个版本的规则。

不同版本的代码可以在同一项目中共存,从而实现向较新版本的逐步迁移。

cargo fix 等工具通过自动应用必要的更改,帮助将代码迁移到较新的版本。

版本使 Rust 能够在不干扰现有项目的情况下发展和改进,使开发人员能够更轻松地按照自己的节奏采用新功能。

三、Crate 📦

Rust 通过 crate 系统实现代码模块化和组织。

crate 是 Rust 的基本编译单元,可被编译为可执行二进制文件或库文件。这种结构促进了代码的模块化、可重用性和关注点分离。

crate 系统还支持代码共享,允许开发者在不同项目间复用功能模块,提高开发效率并减少重复工作。通过 crate,Rust 为大型项目的结构化和协作开发提供了强大的支持。

1、二进制 crate 💻

通常以main.rs作为入口点,编译后生成可执行文件。

// src/main.rs
fn main() {
println!("Hello, world!");
}

2、Library Crate 📦

通常以lib.rs为主文件,编译后生成供其他程序使用的库。

// src/lib.rs
pub fn greet(name: &str) -> String {
println!("Hello, {}!", name);
}

这个库可以被其他 crate 引用。

// src/main.rs
use my_library::greet;

fn main() {
let message = greet("Alice");
println!("{}", message);
}

四、Modules 📚

Rust 中的模块,类似 C++ 中的命名空间,Java 语言中的包。

Rust 语言中一个 crate 可以存放多个模块。

我们常说功能模块,就是用于将函数或结构体按照功能分组。也常常把相似的函数或者实现相同功能的或者共同实现一个功能的函数和结构体划分到一个模块中。

1、定义模块 📦

mod module_name {
fn function_name() {
// ……
}
}

Rust 语言中的模块默认是私有的。如果一个模块或者模块内的函数需要导出为外部使用,则需要添加  pub  关键字。私有的模块不能为外部其它模块或程序所调用。私有模块的所有函数都必须是私有的,而公开的模块,则即可以有公开的函数也可以有私有的函数。

pub mod public_module {
pub fn public_function() {
// ……
}
fn private_function() {
// ……
}
}
mod private_module {
fn private_function() {
// ……
}
}

2、调用模块 📞

use 公开的模块名::函数名;

创建类库: cargo new –lib mylib

// src/lib.rs

/// Adds two numbers and returns the result.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

/// Multiplies two numbers and returns the result.
pub fn multiply(a: i32, b: i32) -> i32 {
a * b
}

进入 mylib 目录执行 cargo build

打开根目录 Cargo.toml

[dependencies]
mylib = { path = "..//mylib" }
// src/main.rs
use mylib::{add, multiply};

fn main() {
let sum = add(5, 10);
let product = multiply(5, 10);

println!("Sum: {}", sum);
println!("Product: {}", product);
}

Rust 允许一个模块中嵌套另一个模块,换种说法,就是允许多层级模块。调用或使用嵌套模块的方法使用两个冒号 (::) 从左到右拼接从外到内的模块即可。

use mod1::mod2::mod3::方法名;

fn main() {
方法名();
}

3、路径 🛤

绝对路径:从 crate root 开始,使用 crate 名或字面值 crate;

相对路径:从当前模块开始,使用 self 、super 或当前模块的标识符;

如果定义的部分和使用的部分总是一起移动,用相对路径,可以独立拆解出来,用绝对路径。

my_crate
├── src
│ ├── lib.rs
│ └── utils.rs
// src/utils.rs
pub fn greet() {
println!("Hello!");
}
// src/lib.rs
pub mod utils;

fn main() {
my_crate::utils::greet();
crate::utils::greet();
}
my_crate
├── src
│ ├── lib.rs
│ └── module_a.rs
│ └── module_b.rs
// src/module_a.rs
pub fn function_a() {
println!("Function A");
self::function_b();
}

pub fn function_b() {
println!("Function B");
}
// src/module_b.rs
pub fn call_function_a() {
super::module_a::function_a();
}
// src/lib.rs
pub mod module_a;
pub mod module_b;

fn main() {
module_b::call_function_a();
}

五、Cargo 🚢

Cargo 是 Rust 的包管理器和构建系统,它简化了管理依赖项、编译包和分发软件等许多任务。这个生态系统使开发人员能够轻松共享库、简化构建过程并轻松管理多个项目。

此外,Rust 的编译器因其有用的错误消息而闻名,不仅告诉你出了什么问题,还建议如何修复。

Cargo 通过管理项目创建、构建和依赖项管理来简化 Rust 开发工作流程。

1、Command ⌨️

`cargo new` 命令使用默认目录结构初始化一个新的 Rust 项目。

添加依赖项很简单;您修改 `Cargo.toml` 文件,`Cargo` 会处理获取和编译必要的 `crate`。

`cargo build` 命令编译项目,`cargo run` 编译并运行项目。这种无缝集成使 Rust 开发更加高效和有组织。

`Cargo` 还支持使用 `cargo test` 运行测试,使用 `cargo doc` 生成文档,以及使用`cargo publish` 将库发布到 `crates.io`。其全面的功能集和易用性使其成为 Rust 生态系统中的重要工具。

`cargo --list` 此命令列出所有可用的 `cargo` 子命令及其简要说明,帮助开发者快速了解 `cargo` 提供的功能和选项,以便更高效地管理 Rust 项目。

使用 `cargo check` 快速检查项目是否有错误,使用 `cargo build` 无需运行即可编译。你可以在 `target/debug/` 中找到正常调试编译的输出结果。使用 `cargo build --release` 可以在 `target/release/` 中生成经过优化的发布版本。

`--verbose` 是一个常见的命令行参数,用于让程序输出更多的运行时信息,以便于更好地了解程序的运行状态和更详尽地进行调试。更具体的含义和行为可能会依据具体的程序而有所差异。
命令说明
cargo new在当前目录下新建一个项目
cargo check分析当前项目并报告项目中的错误,但不会编译任何项目文件
cargo build编译当前项目,生成可执行文件或库,输出到 target 目录
cargo run编译并运行文件  src/main. rs
cargo clean移除当前项目下的  target  目录及目录中的所有子目录和文件
cargo update更新当前项目中的  Cargo. lock  文件列出的所有依赖
cargo test运行项目中的所有测试
cargo doc生成项目的文档,基于代码注释和 Cargo.toml 文件中的信息
cargo publish将当前项目发布到 crates.io,使其可供其他开发者使用
cargo install安装指定的 Rust 工具或库,使其在全局可用
cargo --list列出所有可用的 cargo 子命令及其简要说明,帮助开发者快速了解 cargo 的功能
--verbose使程序输出更多的运行时信息,便于调试和了解程序的运行状态

2、Cargo.toml 📜

Cargo.toml 是用于管理项目元数据和依赖关系的配置文件。

TOML 文件格式与 JavaScript 对象表示法 (JSON) 或 YAML 非标记语言 (YAML) 一样。

在该文件中指定不同的版本约束,开发者可以控制项目所依赖的库的版本。这对于确保项目的稳定性和兼容性至关重要。

Cargo.toml LineMeaning
image = "=0.10.0"Use only the exact version 0.10.0
image = ">=1.0.5"Use version 1.0.5 or higher (including 2.9, if available)
image = ">1.0.5 <1.1.9"Use a version greater than 1.0.5 but less than 1.1.9
image = "<=2.7.10"Use version 2.7.10 or any earlier version
Cargo. toml 行含义
image = "=0.10.0"仅使用确切的版本 0.10.0
image = ">=1.0.5"使用 1.0.5 或更高版本(甚至 2.9,如果其可用的话)
image = ">1.0.5 <1.1.9"使用高于 1.0.5 但低于 1.1.9 的版本
image = "<=2.7.10"使用 2.7.10 或更早的任何版本

3、Cargo.lock 🔒

当第一次构建项目时,Cargo 会输出一个 Cargo.lock 文件,以记录它使用的每个 crate 的确切版本。以后的构建都将参考此文件并继续使用相同的版本。仅当你要求 Cargo 升级时它才会升级到更新版本,方法是手动增加 Cargo.toml 文件中的版本号或运行  cargo update

Cargo.lock 是自动生成的,通常不用手动编辑。不过,如果此项目是可执行文件,那你就应该将 Cargo.lock 提交到版本控制。这样,构建项目的每个人总是会获得相同的版本。Cargo.lock文件的版本历史中会记录这些依赖项更新。

六、优化 🚀

在 Rust 中,优化可以显著提高程序的性能。以下将介绍几种常用的优化技巧,包括编译配置、链接时优化、panic 处理以及编译时间优化。

1、编译配置 ⚙

Debug 模式 (cargo build):

  • 配置区段: [profile.dev]
  • 优点: 编译速度快,适合调试
  • 缺点: 运行速度较慢,不进行优化

Release 模式 (cargo build --release):

  • 配置区段: [profile.release]
  • 优点: 运行速度快,进行多种优化
  • 缺点: 编译时间长,生成的二进制文件可能较大

在开发过程中使用 debug 模式,而在发布时使用 release 模式是常见做法。

默认的是 debug 构建—— 如果你运行  cargo buildcargo runrustc,并且不带其他选项,就会生成 debug 构建。 debug 构建对调试很有用,但是并不优化。

在 Release 模式下编译你的 Rust 程序时,编译器会进行一系列的优化,例如内联,循环展开,死代码删除,常量折叠等。这些优化会使你的程序运行得更快。然而,这些优化也会使你的程序的编译时间更长,并可能使生成的二进制文件更大。

// src/main.rs
fn fibonacci(n: u64) -> u64 {
if n <= 1 {
return n;
}
fibonacci(n - 1) + fibonacci(n - 2)
}

fn main() {
let n = 40;
println!("Fibonacci({}) = {}", n, fibonacci(n));
}
# Debug模式
$ cargo run
Compiling fib v0.1.0 (/path/to/fib)
Finished dev [unoptimized + debuginfo] target(s) in 0.50s
Running `target/debug/fib`
Fibonacci(40) = 102334155
# 运行时间:约5秒

# Release模式
$ cargo run --release
Compiling fib v0.1.0 (/path/to/fib)
Finished release [optimized] target(s) in 0.72s
Running `target/release/fib`
Fibonacci(40) = 102334155
# 运行时间:约0.05秒
  • [unoptimized + debuginfo]  表示生成的是 debug 构建。编译后的代码会放在  target/debug/  目录中。 cargo run  会运行 debug 构建的程序。

  • [optimized]  表示生成的是 release 构建。编译好的代码会放在  target/release/  目录中。 cargo run --release  会运行 release 构建。

2、链接时优化 🔨

链接时优化 (Link-time optimization, LTO) 是一种适用于整个程序的优化技术,以增加构建时间为代价,可以提高 10%-20% 或更多的运行时性能,对于单个 Rust 程序,通常用编译时间换取运行性能是值得的(启用 LTO 后,编译时间略有增加,但运行时间进一步减少)。

启用 LTO 最简单的方法是,向  Cargo.toml  中添加下列行,然后进行 release 构建。

[profile.release]
lto = true

另外,在  Cargo.toml  中使用  lto = "thin"  则会启用 “轻量级”(thin) LTO——一种不那么激进的 LTO 形式,通常与重量级 LTO 一样有效,但不会过多增加构建时间。

[profile.release]
lto = "thin"
cargo run --release

3、panic! 时 abort 🔗

如果不需要捕获或展开 panic,可以配置在 panic 时直接中止程序,这样可以减小二进制文件大小并略微提升性能:

[profile.release]
panic = "abort"
# 优化前
$ ls -lh target/release/fib
-rwxr-xr-x 2 user user 3.8M Aug 13 10:00 target/release/fib

# 优化后
$ cargo build --release
$ ls -lh target/release/fib
-rwxr-xr-x 2 user user 3.6M Aug 13 10:05 target/release/fib

3、更快的链接器 🚀

这里推荐 lld,它支持 ELF,PE/COFF,Mach-O,wasm 等等。

通过命令行指定使用 lld,你可以在你的构建命令前加上 RUSTFLAGS="-C link-arg=-fuse-ld=lld"

或在config.toml中配置:

[build]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
# 使用默认链接器
$ time cargo build --release
Finished release [optimized] target(s) in 25.32s

real 0m25.320s
user 0m24.156s
sys 0m1.164s

# 使用lld
$ RUSTFLAGS="-C link-arg=-fuse-ld=lld" time cargo build --release
Finished release [optimized] target(s) in 20.15s

real 0m20.150s
user 0m19.228s
sys 0m0.922s

4、增量编译 ➕

Rust 编译器支持增量编译,避免在重编译的时候做重复的工作。它可以大大提升编译速度,但有时会让生成的可执行程序运行的慢一些。因此,它只默认为调试构建启用。如果你也想为发布构建启用,把这些加到 Cargo.toml

[profile.release]
incremental = true
$ touch src/main.rs
$ time cargo build --release
Compiling project v0.1.0 (/path/to/project)
Finished release [optimized] target(s) in 1m 45s

real 1m45.150s
user 1m43.228s
sys 0m1.922s


$ touch src/main.rs
$ time cargo build --release
Compiling project v0.1.0 (/path/to/project)
Finished release [optimized] target(s) in 0m 25s

real 0m25.120s
user 0m24.156s
sys 0m0.964s