Cargo是Rust的包管理器和构建工具。
1.基本使用
1.1 帮助
运行cargo help列出cargo的可用命令,基于cargo的不同版本,以下输出内容可能会有不同:
cargo help
Rust's package manager
Usage: cargo [+toolchain] [OPTIONS] [COMMAND]
cargo [+toolchain] [OPTIONS] -Zscript <MANIFEST_RS> [ARGS]...
Options:
-V, --version Print version info and exit
--list List installed commands
--explain <CODE> Provide a detailed explanation of a rustc error message
-v, --verbose... Use verbose output (-vv very verbose/build.rs output)
-q, --quiet Do not print cargo log messages
--color <WHEN> Coloring: auto, always, never
-C <DIRECTORY> Change to DIRECTORY before doing anything (nightly-only)
--locked Assert that `Cargo.lock` will remain unchanged
--offline Run without accessing the network
--frozen Equivalent to specifying both --locked and --offline
--config <KEY=VALUE> Override a configuration value
-Z <FLAG> Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Print help
Commands:
build, b Compile the current package
check, c Analyze the current package and report errors, but don't build object files
clean Remove the target directory
doc, d Build this package's and its dependencies' documentation
new Create a new cargo package
init Create a new cargo package in an existing directory
add Add dependencies to a manifest file
remove Remove dependencies from a manifest file
run, r Run a binary or example of the local package
test, t Run the tests
bench Run the benchmarks
update Update dependencies listed in Cargo.lock
search Search registry for crates
publish Package and upload this package to the registry
install Install a Rust binary
uninstall Uninstall a Rust binary
... See all commands with --list
See 'cargo help <command>' for more information on a specific command.1.2 创建应用程序(application和库(library)
1.2.1 创建应用程序项目
cargo new foo
Creating binary (application) `foo` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html上面的命令创建了一个名称为foo的应用程序。Cargo.toml是cargo的配置文件。
tree foo
foo
├── Cargo.toml
└── src
└── main.rs
2 directories, 2 files使用cargo run运行应用程序:
cd foo
cargo run
Compiling foo v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.53s
Running `target/debug/foo`
Hello, world!1.2.2 创建库项目
cargo new bar --lib
Creating library `bar` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html加上--lib后创建的是库项目。
tree bar
bar
├── Cargo.toml
└── src
└── lib.rs
2 directories, 2 fileslib.rs中伴随着单元测试,使用cargo test运行测试。
cd bar
Compiling bar v0.1.0
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.69s
Running unittests src/lib.rs (target/debug/deps/bar-7f4163e6e3d09c52)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests bar
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s1.3 cargo clean, check, build, run, test
clean, check, build, run, test可能是使用比较多的几个命令。
| Command (Short) | Command (Long) | Description |
|---|---|---|
| c | check | 分析包中的错误,但不生成最终输出(不构建) |
| b | build | 编译当前包 |
| r | run | 执行本地包中的二进制文件或示 |
| t | test | 运行为包定义的测试 |
| clean | 删除包含构建产物的target目录 |
cargo check只检查代码,不生成可执行文件或库。 它主要进行语法分析、类型检查等,确保代码在语义上是正确的,但不会进行代码生成、链接等后续的构建步骤。因此,cargo check的速度通常比cargo build快得多。cargo build会完整地构建项目,包括编译、链接等步骤,最终生成可执行文件或库。 它会进行所有必要的步骤,将源代码转换成可以运行的程序。
1.4 切换toolchain
从cargo help的输出中,可以看到cargo命令的使用方法是cargo [+toolchain] [OPTIONS] [COMMAND]。
cargo后边可以跟着toolchain,那么什么是toolchain呢?
关于toolchain的详细介绍可以查看«The rustup book»中关于toolchain的内容。
使用rustup show命令可以查看已经激活和安装的toolchain和profile。
rustup show
Default host: x86_64-unknown-linux-gnu
rustup home: ~/.rustup
installed targets for active toolchain
--------------------------------------
wasm32-unknown-unknown
x86_64-apple-darwin
x86_64-unknown-linux-gnu
active toolchain
----------------
stable-x86_64-unknown-linux-gnu (default)
rustc 1.83.0 (90b35a623 2024-11-26)可以看到当前激活的toolchain是stable-x86_64-unknown-linux-gnu。toolchain可以理解成是"Rust发布渠道channel" + “架构” + “平台"的组合。例如stable-x86_64-unknown-linux-gnu就是Rust的稳定渠道在x86_64 CPU架构的Linux平台上。
使用rustup toolchain命令也可以查询安装的toolchain,同时还支持对toolchain对管理、安装卸载等操作。
rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)例如在安装nightly发布渠道的toolchain:
rustup toolchain install nightly
rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu指定使用nightly toolchain运行cargo。
cargo +nightly test
cargo +nightly version
cargo 1.85.0-nightly (c86f4b3a1 2024-12-24)
cargo version
cargo 1.83.0 (5ffbef321 2024-10-29)例如在安装1.82.0发布渠道的toolchain:
rustup toolchain install 1.82.0
rustup toolchain list
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu
1.82.0-x86_64-unknown-linux-gnu指定使用1.82.0运行cargo:
cargo +1.82.0 version
cargo 1.82.0 (8f40fc59f 2024-08-21)使用rustup override set命令可以将当前目录及子目录设置默认的toolchain。
rustup override set 1.82.0
info: override toolchain for 'bar' set to '1.82.0-x86_64-unknown-linux-gnu'
cargo version
cargo 1.82.0 (8f40fc59f 2024-08-21)override的配置记录在~/.rustup/settings.toml配置文件中。
使用rustup override unset将当前目录及子目录设置默认的toolchain移除:
rustup override unset
cargo version
info: override toolchain for 'bar' removed
cargo 1.83.0 (5ffbef321 2024-10-29)2.依赖管理
在Rust社区软件包被称为crates,creates即包含应用程序也包含库。crates.io是Rust社区的package registry。
2.1 添加依赖
cargo-edit是cargo的扩展,允许通过命令行修改Cargo.toml来添加、移除和升级依赖项。
例如添加rand crate作为项目的依赖。
cargo add randCargo.toml中会加入对rand的依赖:
[dependencies]
rand = "0.8.5"依赖中crate的版本号遵循语义版本控制,使用major.minor.patch模式。即x.y.z。支持对版本加入符号,例如:^x.y.z, ~x.y.z, x.*, x.y.*, >x.y, <x.y, =x.y.z。
| 符号 | 描述 | 示例 | 最小版本 | 最大版本 |
|---|---|---|---|---|
x.y.z | 精确匹配指定的版本号,x 为主版本号,y 为次版本号,z 为补丁版本号。 | 1.2.3 | 1.2.3 | 1.2.3 |
^x.y.z | 表示兼容指定的版本,允许次版本号和补丁版本号升级,但主版本号不变。 | ^1.2.3 | 1.2.3 | <2.0.0 |
~x.y.z | 表示兼容指定版本的补丁版本,允许补丁版本号升级,但次版本号和主版本号不变。 | ~1.2.3 | 1.2.3 | <1.3.0 |
x.* | 匹配所有次版本和补丁版本,允许任意次版本号和补丁版本号,但主版本号不变。 | 1.* | 1.0.0 | <2.0.0 |
x.y.* | 匹配所有补丁版本,允许任意补丁版本号,但主版本号和次版本号不变。 | 1.2.* | 1.2.0 | <1.3.0 |
>x.y | 匹配高于指定版本的所有版本。 | >1.2 | 1.2.1 | 无上限 |
<x.y.z | 匹配低于指定版本的所有版本。 | <1.2.3 | 无下限 | 1.2.2 |
>=x.y.z | 匹配指定版本或更高的所有版本。 | >=1.2.3 | 1.2.3 | 无上限 |
<=x.y.z | 匹配指定版本或更低的所有版本。 | <=1.2.3 | 无下限 | 1.2.3 |
* | 匹配所有版本。 | * | 无下限 | 无上限 |
2.2 Cargo.lock
Cargo.lock文件是Rust项目中Cargo包管理器用于锁定依赖版本的重要文件。它确保项目在不同环境和不同时间构建时,始终使用相同版本的依赖库,从而避免因依赖版本不一致而导致的问题。通过Cargo.lock是实现可重现的构建,无论在哪个机器、何时构建项目,Cargo都会使用其中记录的完全相同的依赖版本。这保证了构建的可重现性,避免了因依赖版本变化导致构建失败或行为不一致。
cargo update命令会更新Cargo.lock文件和其中的依赖。
对于库项目建议将Cargo.lock添加到.gitignore中,这样可以让下游crate可以根据需要更新间接依赖项。
对于应用程序项目建议将Cargo.lock与Cargo.toml一起提交,这样有助于确保将来第三方库发生变化时,重新构建项目时与之前构建项目所依赖的版本行为一致
3. features
https://doc.rust-lang.org/cargo/reference/features.html#features
Cargo的features提供了一种机制,用于实现条件编译和可选依赖项。在Cargo.toml的[features]中,可以为一个package定义一组命名特性,每个特性都可以被启用或禁用。构建时的包特性可以通过命令行标志(例如--features)启用,而依赖项的特性则可以在Cargo.toml中的依赖声明中启用。
Cargo的features机制可以帮助我们控制package和依赖项在构建时的行为。
3.1 package features(包特性)
features在Cargo.toml的[features]中定义。每个特性都指定一个数组,该数组列出了它所启用的其他特性或可选依赖项。以下示例展示了在一个2D图像处理库中,如何使用特性来可选地支持不同的图像格式:
[features]
# 定义一个名为webp的feature,该特性不会启用任何其他features。
webp = []定义了这个特性后,可以使用cfg表达式在编译时有条件地包含支持该特性的代码。例如,包的lib.rs文件中可以包含以下内容:
// 这会有条件地包含一个实现webp支持的模块。即根据feature进行条件编译
#[cfg(feature = "webp")]
pub mod webp;Cargo使用rustc的–cfg flag在包中设置特性,代码可以通过cfg attribute或cfg macro.来检测这些特性是否存在。
特性可以列出其他需要启用的特性。例如,ico图像格式可以包含bmp和png图像,因此当启用ico格式时,应确保这些其他特性也被启用:
[features]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []用命令启用特性:
cargo build --features "bmp png"default feature
默认情况下,所有特性都是禁用的,除非显式启用。可以通过指定default feature特性来更改这一点:
[features]
default = ["ico", "webp"]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []当包被构建时,默认特性会被启用。此行为可以通过以下方式更改:
--no-default-features命令行标志禁用包的默认特性。- 在依赖声明中可以指定
default-features = false选项,以禁用默认特性。
可选依赖
依赖项可以被标记为optional可选的,这意味着默认情况下它们不会被编译。例如,假设我们的2D图像处理库使用一个外部包来处理gif图像。这可以这样表示:
[dependencies]
gif = { version = "0.11.1", optional = true }默认情况下,这个可选依赖会隐式定义一个看起来像这样的特性:
[features]
gif = ["dep:gif"]这意味着只有在启用 gif 特性时,这个依赖才会被包含。代码中可以使用相同的 cfg(feature = "gif") 语法,依赖也可以像启用任何特性一样启用,例如使用命令行选项--features gif。
在某些情况下,可能不希望暴露与可选依赖同名的特性。例如,也许这个可选依赖是一个内部细节,或者希望将多个可选依赖分组在一起,或者只是想使用一个更好的名称。如果在[features]中的任何地方使用dep: 前缀来指定可选依赖,这将禁用隐式特性。
例如,假设为了支持avif图像格式,我们的库需要启用另外两个依赖:
[dependencies]
ravif = { version = "0.6.3", optional = true }
rgb = { version = "0.8.25", optional = true }
[features]
avif = ["dep:ravif", "dep:rgb"]注意
建议不要过分packge features。当我们发现自己正在创建带有大量features的超级包时,但如果更好的方式是将包拆分为更小的独立子包,这种模式相当常见。
3.2 dependency features(依赖特性)
依赖的特性可以在依赖声明中启用。features key指示要启用哪些特性:
[dependencies]
# 为serde启用derive特性
serde = { version = "1.0.118", features = ["derive"] }可以通过default-features = false来禁用默认特性:
[dependencies]
flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }注意
这可能无法确保默认特性被禁用。如果另一个依赖项包含了flate2,但没有指定default-features = false,那么默认特性将会被启用。
依赖的特性也可以在[features]中启用。语法为"package-name/feature-name”。例如:
[dependencies]
jpeg-decoder = { version = "0.1.20", default-features = false }
[features]
# Enables parallel processing support by enabling the "rayon" feature of jpeg-decoder.
parallel = ["jpeg-decoder/rayon"]“package-name/feature-name"语法也会启用package-name,如果它是一个可选依赖项。通常,这不是想要的行为。可以在语法中添加一个?,如 “package-name?/feature-name”,这将仅在其他内容启用可选依赖时启用指定的特性。
例如,假设我们为库添加了一些序列化支持,并且它需要在一些可选依赖中启用相应的特性。可以这样做:
[dependencies]
serde = { version = "1.0.133", optional = true }
rgb = { version = "0.8.25", optional = true }
[features]
serde = ["dep:serde", "rgb?/serde"]在这个示例中,启用serde特性将启用serde依赖。它还会启用rgb依赖的serde特性,但只有在其他地方启用了rgb依赖时才会生效。
4.workspace
workspace是一个包含一个或多个包的集合,这些包被称为workspace成员,并一起进行管理。
workspace的关键点是:
- 可以在所有workspace成员上运行常用命令,例如
cargo check --workspace。 - 所有包共享一个公共的
Cargo.lock文件,该文件位于workspace根目录。 - 所有包共享一个公共的输出目录,默认是位于workspace根目录的
target目录。 - 共享包元数据,例如通过
workspace.package。 Cargo.toml中的[patch]、[replace]和[profile.*]部分仅在根清单中识别,在成员包的清单中会被忽略。
workspace的根 Cargo.toml 支持以下部分:
- [workspace] — 定义一个workspace。
- resolver — 设置要使用的依赖解析器。
- members — 要包含在workspace中的包。
- exclude — 要排除在workspace之外的包。
- default-members — 在未选择特定包时要操作的包。
- package — 包中继承的keys。
- dependencies — 包依赖中继承的keys。
- lints — 包中继承的lint keys。
- metadata — 外部工具的额外设置。
[patch]— 覆盖依赖项。[replace]— 覆盖依赖项(已弃用)。[profile]— 编译器设置和优化。
Cargo的workspace功能允许我们将大型crate拆分成多个单独的crate,并将这些crate分组到共享单个Cargo.lock的workspace中。
5. build script
https://doc.rust-lang.org/cargo/reference/build-scripts.html
有些包需要编译第三方非 Rust 代码,例如 C 库。其他包需要链接到 C 库,这些库可以位于系统中,或者可能需要从源代码构建。还有一些包需要在构建之前执行代码生成等功能(比如parser generators))。
Cargo的目标不是替代那些在这些任务上已优化良好的工具,但它通过自定义构建脚本与这些工具进行集成。将名为build.rs的文件放置在包的根目录中,Cargo会编译该脚本,并在构建包之前执行它。
// Example custom build script.
fn main() {
// Tell Cargo that if the given file changes, to rerun this build script.
println!("cargo::rerun-if-changed=src/hello.c");
// Use the `cc` crate to build a C file and statically link it.
cc::Build::new()
.file("src/hello.c")
.compile("hello");
}Cargo提供构建时功能,允许在Rust脚本中指定构建时操作。该脚本包含一个rust main函数以及想要包含的任何其他代码,包括构建依赖项,这些依赖项在Cargo.toml中的[build-dependencies]部分中指定。
该脚本通过将特殊格式的命令打印到stdout与Cargo进行通信,Cargo将解释并执行这些命令。
构建脚本的一些常见用途包括:
- 编译 C 或 C++ 代码
- 在编译之前对 Rust 代码运行自定义预处理器
- 使用 protoc-rust 生成 Rust protobuf 代码
- 从模板生成 Rust 代码
- 运行平台检查,例如验证库的存在和查找库