青蛙小白
博客 / 2024/12

Cargo简明教程

2024/12/27 · — 字 · 阅读约 — 分钟 ·
目录

Cargo是Rust的包管理器和构建工具。

Cargo 简明教程

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 files

lib.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.00s

1.3 cargo clean, check, build, run, test

clean, check, build, run, test可能是使用比较多的几个命令。

Command (Short)Command (Long)Description
ccheck分析包中的错误,但不生成最终输出(不构建)
bbuild编译当前包
rrun执行本地包中的二进制文件或示
ttest运行为包定义的测试
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 rand

Cargo.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.31.2.31.2.3
^x.y.z表示兼容指定的版本,允许次版本号和补丁版本号升级,但主版本号不变。^1.2.31.2.3<2.0.0
~x.y.z表示兼容指定版本的补丁版本,允许补丁版本号升级,但次版本号和主版本号不变。~1.2.31.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.21.2.1无上限
<x.y.z匹配低于指定版本的所有版本。<1.2.3无下限1.2.2
>=x.y.z匹配指定版本或更高的所有版本。>=1.2.31.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 attributecfg 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

https://doc.rust-lang.org/cargo/reference/workspaces.html

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 代码
  • 运行平台检查,例如验证库的存在和查找库
TAGS # rust
评论