Cargo简明教程
📅 2024-12-27 | 🖱️
Cargo是Rust的包管理器和构建工具。
1.基本使用 #
1.1 帮助 #
运行cargo help
列出cargo的可用命令,基于cargo的不同版本,以下输出内容可能会有不同:
1cargo help
2Rust's package manager
3
4Usage: cargo [+toolchain] [OPTIONS] [COMMAND]
5 cargo [+toolchain] [OPTIONS] -Zscript <MANIFEST_RS> [ARGS]...
6
7Options:
8 -V, --version Print version info and exit
9 --list List installed commands
10 --explain <CODE> Provide a detailed explanation of a rustc error message
11 -v, --verbose... Use verbose output (-vv very verbose/build.rs output)
12 -q, --quiet Do not print cargo log messages
13 --color <WHEN> Coloring: auto, always, never
14 -C <DIRECTORY> Change to DIRECTORY before doing anything (nightly-only)
15 --locked Assert that `Cargo.lock` will remain unchanged
16 --offline Run without accessing the network
17 --frozen Equivalent to specifying both --locked and --offline
18 --config <KEY=VALUE> Override a configuration value
19 -Z <FLAG> Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
20 -h, --help Print help
21
22Commands:
23 build, b Compile the current package
24 check, c Analyze the current package and report errors, but don't build object files
25 clean Remove the target directory
26 doc, d Build this package's and its dependencies' documentation
27 new Create a new cargo package
28 init Create a new cargo package in an existing directory
29 add Add dependencies to a manifest file
30 remove Remove dependencies from a manifest file
31 run, r Run a binary or example of the local package
32 test, t Run the tests
33 bench Run the benchmarks
34 update Update dependencies listed in Cargo.lock
35 search Search registry for crates
36 publish Package and upload this package to the registry
37 install Install a Rust binary
38 uninstall Uninstall a Rust binary
39 ... See all commands with --list
40
41See 'cargo help <command>' for more information on a specific command.
1.2 创建应用程序(application和库(library) #
1.2.1 创建应用程序项目 #
1cargo new foo
2 Creating binary (application) `foo` package
3note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
上面的命令创建了一个名称为foo
的应用程序。Cargo.toml
是cargo的配置文件。
1tree foo
2foo
3├── Cargo.toml
4└── src
5 └── main.rs
6
72 directories, 2 files
使用cargo run
运行应用程序:
1cd foo
2cargo run
3 Compiling foo v0.1.0
4 Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.53s
5 Running `target/debug/foo`
6Hello, world!
1.2.2 创建库项目 #
1cargo new bar --lib
2 Creating library `bar` package
3note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
加上--lib
后创建的是库项目。
1tree bar
2bar
3├── Cargo.toml
4└── src
5 └── lib.rs
6
72 directories, 2 files
lib.rs
中伴随着单元测试,使用cargo test
运行测试。
1cd bar
2 Compiling bar v0.1.0
3 Finished `test` profile [unoptimized + debuginfo] target(s) in 1.69s
4 Running unittests src/lib.rs (target/debug/deps/bar-7f4163e6e3d09c52)
5
6running 1 test
7test tests::it_works ... ok
8
9test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
10
11 Doc-tests bar
12
13running 0 tests
14
15test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
1.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。
1rustup show
2Default host: x86_64-unknown-linux-gnu
3rustup home: ~/.rustup
4
5installed targets for active toolchain
6--------------------------------------
7
8wasm32-unknown-unknown
9x86_64-apple-darwin
10x86_64-unknown-linux-gnu
11
12active toolchain
13----------------
14
15stable-x86_64-unknown-linux-gnu (default)
16rustc 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对管理、安装卸载等操作。
1rustup toolchain list
2stable-x86_64-unknown-linux-gnu (default)
例如在安装nightly发布渠道的toolchain:
1rustup toolchain install nightly
2
3rustup toolchain list
4stable-x86_64-unknown-linux-gnu (default)
5nightly-x86_64-unknown-linux-gnu
指定使用nightly toolchain运行cargo。
1cargo +nightly test
2
3cargo +nightly version
4cargo 1.85.0-nightly (c86f4b3a1 2024-12-24)
5
6cargo version
7cargo 1.83.0 (5ffbef321 2024-10-29)
例如在安装1.82.0
发布渠道的toolchain:
1rustup toolchain install 1.82.0
2
3rustup toolchain list
4stable-x86_64-unknown-linux-gnu (default)
5nightly-x86_64-unknown-linux-gnu
61.82.0-x86_64-unknown-linux-gnu
指定使用1.82.0运行cargo:
1cargo +1.82.0 version
2cargo 1.82.0 (8f40fc59f 2024-08-21)
使用rustup override set
命令可以将当前目录及子目录设置默认的toolchain。
1rustup override set 1.82.0
2info: override toolchain for 'bar' set to '1.82.0-x86_64-unknown-linux-gnu'
3
4cargo version
5cargo 1.82.0 (8f40fc59f 2024-08-21)
override的配置记录在~/.rustup/settings.toml
配置文件中。
使用rustup override unset
将当前目录及子目录设置默认的toolchain移除:
1rustup override unset
2cargo version
3info: override toolchain for 'bar' removed
4cargo 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作为项目的依赖。
1cargo add rand
Cargo.toml
中会加入对rand的依赖:
1[dependencies]
2rand = "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图像处理库中,如何使用特性来可选地支持不同的图像格式:
1[features]
2# 定义一个名为webp的feature,该特性不会启用任何其他features。
3webp = []
定义了这个特性后,可以使用cfg表达式在编译时有条件地包含支持该特性的代码。例如,包的lib.rs
文件中可以包含以下内容:
1// 这会有条件地包含一个实现webp支持的模块。即根据feature进行条件编译
2#[cfg(feature = "webp")]
3pub mod webp;
Cargo使用rustc
的–cfg flag在包中设置特性,代码可以通过cfg attribute或cfg macro.来检测这些特性是否存在。
特性可以列出其他需要启用的特性。例如,ico图像格式可以包含bmp和png图像,因此当启用ico格式时,应确保这些其他特性也被启用:
1[features]
2bmp = []
3png = []
4ico = ["bmp", "png"]
5webp = []
用命令启用特性:
1cargo build --features "bmp png"
default
feature
默认情况下,所有特性都是禁用的,除非显式启用。可以通过指定default
feature特性来更改这一点:
1[features]
2default = ["ico", "webp"]
3bmp = []
4png = []
5ico = ["bmp", "png"]
6webp = []
当包被构建时,默认特性会被启用。此行为可以通过以下方式更改:
--no-default-features
命令行标志禁用包的默认特性。- 在依赖声明中可以指定
default-features = false
选项,以禁用默认特性。
可选依赖
依赖项可以被标记为optional
可选的,这意味着默认情况下它们不会被编译。例如,假设我们的2D图像处理库使用一个外部包来处理gif图像。这可以这样表示:
1[dependencies]
2gif = { version = "0.11.1", optional = true }
默认情况下,这个可选依赖会隐式定义一个看起来像这样的特性:
1[features]
2gif = ["dep:gif"]
这意味着只有在启用 gif 特性时,这个依赖才会被包含。代码中可以使用相同的 cfg(feature = "gif")
语法,依赖也可以像启用任何特性一样启用,例如使用命令行选项--features gif
。
在某些情况下,可能不希望暴露与可选依赖同名的特性。例如,也许这个可选依赖是一个内部细节,或者希望将多个可选依赖分组在一起,或者只是想使用一个更好的名称。如果在[features]
中的任何地方使用dep:
前缀来指定可选依赖,这将禁用隐式特性。
例如,假设为了支持avif图像格式,我们的库需要启用另外两个依赖:
1[dependencies]
2ravif = { version = "0.6.3", optional = true }
3rgb = { version = "0.8.25", optional = true }
4
5[features]
6avif = ["dep:ravif", "dep:rgb"]
注意
建议不要过分packge features。当我们发现自己正在创建带有大量features的超级包时,但如果更好的方式是将包拆分为更小的独立子包,这种模式相当常见。
3.2 dependency features(依赖特性) #
依赖的特性可以在依赖声明中启用。features key指示要启用哪些特性:
1[dependencies]
2# 为serde启用derive特性
3serde = { version = "1.0.118", features = ["derive"] }
可以通过default-features = false
来禁用默认特性:
1[dependencies]
2flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
注意
这可能无法确保默认特性被禁用。如果另一个依赖项包含了flate2,但没有指定
default-features = false
,那么默认特性将会被启用。
依赖的特性也可以在[features]
中启用。语法为"package-name/feature-name”。例如:
1[dependencies]
2jpeg-decoder = { version = "0.1.20", default-features = false }
3
4[features]
5# Enables parallel processing support by enabling the "rayon" feature of jpeg-decoder.
6parallel = ["jpeg-decoder/rayon"]
“package-name/feature-name"语法也会启用package-name
,如果它是一个可选依赖项。通常,这不是想要的行为。可以在语法中添加一个?
,如 “package-name?/feature-name”,这将仅在其他内容启用可选依赖时启用指定的特性。
例如,假设我们为库添加了一些序列化支持,并且它需要在一些可选依赖中启用相应的特性。可以这样做:
1[dependencies]
2serde = { version = "1.0.133", optional = true }
3rgb = { version = "0.8.25", optional = true }
4
5[features]
6serde = ["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会编译该脚本,并在构建包之前执行它。
1// Example custom build script.
2fn main() {
3 // Tell Cargo that if the given file changes, to rerun this build script.
4 println!("cargo::rerun-if-changed=src/hello.c");
5 // Use the `cc` crate to build a C file and statically link it.
6 cc::Build::new()
7 .file("src/hello.c")
8 .compile("hello");
9}
Cargo提供构建时功能,允许在Rust脚本中指定构建时操作。该脚本包含一个rust main函数以及想要包含的任何其他代码,包括构建依赖项,这些依赖项在Cargo.toml
中的[build-dependencies]
部分中指定。
该脚本通过将特殊格式的命令打印到stdout与Cargo进行通信,Cargo将解释并执行这些命令。
构建脚本的一些常见用途包括:
- 编译 C 或 C++ 代码
- 在编译之前对 Rust 代码运行自定义预处理器
- 使用 protoc-rust 生成 Rust protobuf 代码
- 从模板生成 Rust 代码
- 运行平台检查,例如验证库的存在和查找库