Rust Cargo 是什么?简而言之,cargo 是 Rust 中使用的包管理器。就像其他编程语言中的包管理器一样,它通常会管理包的依赖关系,但在 Cargo 中不仅仅是依赖关系,还有更多。
Cargo is the Rust package manager. It is a tool that allows Rust packages to declare their various dependencies and ensure that you’ll always get a repeatable build.。
Source: https://doc.rust-lang.org/cargo/guide/why-cargo-exists.html
Rust Cargo
除了管理包的依赖项之外,包管理器还为我们提供了很多管理构建、测试和许多事情的能力。 Cargo 将帮助我们做四件事
- 引入两个元数据文件,其中包含各种包信息。
- 获取并构建包的依赖项。
- 使用正确的参数调用 rustc 或其他构建工具来构建您的包。
- 引入一些conventions,使 Rust 包的使用更加容易。
Rust Cargo 还提供了一个集中式注册表,我们作为开发人员可以从中获取板条箱。
A registry is a service that contains a collection of downloadable crates that can be installed or used as dependencies for a package. The default registry in the Rust ecosystem is crates.io. The registry has an index which contains a list of all crates, and tells Cargo how to download the crates that are needed. A Rust crate is either a library or an executable program, referred to as either a library crate or a binary crate, respectively.
作为 Rust 语言和生态系统的新手,我非常喜欢 Rust Cargo 的一些功能。
代码库布局
在其他编程语言中,关于它们如何管理代码包文件布局有很多选项,如 CMake,实际上我很讨厌这一点。太多的选项对我来说意味着太多的事情需要考虑。 Cargo 提供了一个管理我们代码库的惯例。结构是这样的:
Cargo.lock
├── Cargo.toml
├── src/
│ ├── lib.rs
│ ├── main.rs
│ └── bin/
│ ├── named-executable.rs
│ ├── another-executable.rs
│ └── multi-file-executable/
│ ├── main.rs
│ └── some_module.rs
├── benches/
│ ├── large-input.rs
│ └── multi-file-bench/
│ ├── main.rs
│ └── bench_module.rs
├── examples/
│ ├── simple.rs
│ └── multi-file-example/
│ ├── main.rs
│ └── ex_module.rs
└── tests/
├── some-integration-tests.rs
└── multi-file-test/
├── main.rs
└── test_module.rs
结构说明:
- Cargo.toml 和 Cargo.lock 存储在包的根目录中。
- 源代码位于 src 目录中。
- 默认库文件是 src/lib.rs。
- 默认的可执行文件是 src/main.rs。
- 其他可执行文件可以放置在 src/bin/ 中。
- 基准测试位于 benches 目录中。
- 示例位于示例目录中。
- 集成测试位于测试目录中。
来源:https://doc.rust-lang.org/cargo/guide/project-layout.html
Manifest
Cargo Manifest
是包的配置、元数据和描述。它将告诉我们如何构建包、包描述、依赖项以及许多事情。 清单本身将采用名为 Cargo.toml 的文件的形式。此清单的示例内容
[package]
name = "hello_world" # the name of the package
version = "0.1.0" # the current version, obeying semver
authors = ["Alice ", "Bob "]
为什么我喜欢这种格式?它简单易学。不存在像自定义 DSL、Makefile等这些奇葩的脚本。一切都是明确且可预测的。
Dependencies
对于包的依赖关系,与其他编程语言包管理器一样,我们可以定义我们依赖什么类型的库以及我们需要哪个版本。有趣的是,我们还可以定义要从中激活哪个功能标志,我们稍后将讨论此功能。 对于版本,Rust Cargo 遵循 Semver(语义版本控制)格式。例子:
1.2.3 := >=1.2.3, <2.0.0
1.2 := >=1.2.0, <2.0.0
1 := >=1.0.0, <2.0.0
0.2.3 := >=0.2.3, <0.3.0
0.2 := >=0.2.0, <0.3.0
0.0.3 := >=0.0.3, <0.0.4
0.0 := >=0.0.0, <0.1.0
0 := >=0.0.0, <1.0.0
有三个来源可以获取包的
- git
- path
- crates.io(或 private registry)
例子:
- 来自private registry:
[dependencies]
some-crate = { version = "1.0", registry = "my-registry" }
- 来自 git:
[dependencies]
regex = { git = "https://github.com/rust-lang/regex.git" }
- 来自 path:
[dependencies]
hello_utils = { path = "../hello/hello_utils" }
Workspace
我很喜欢 Rust Cargo 帮助我们维护一个可能由多个包/板条箱组成的大型代码库的方式,它被称为 Workspace 。
A workspace is a collection of one or more packages, called workspace members, that are managed together.
Source: https://doc.rust-lang.org/cargo/reference/workspaces.html我发现一些开源项目已经使用了这种方法:
- Solana Blockchain: https://github.com/solana-labs/solana/blob/master/Cargo.toml
- NEAR Blockchain: https://github.com/near/nearcore/blob/master/Cargo.toml
- Polkadot SDK: https://github.com/paritytech/polkadot-sdk/blob/master/Cargo.toml
- Tokio: https://github.com/tokio-rs/tokio/blob/master/Cargo.toml
我认为 Rust 维护所有这些大型代码库的方式非常优雅。我们可以维护多个成员以包含在工作区项目中或从中排除。例子:
[workspace]
members = ["member1", "path/to/member2", "crates/*"]
exclude = ["crates/foo", "path/to/other"]
从这个例子中,如果我们使用工作空间,可以声明从 path/to/member2 依赖于 member1 的依赖关系,这意味着工作空间内的每个内部包都可以相互引用。例如:
# [PROJECT_DIR]/bar/Cargo.toml
[package]
name = "bar"
version = "0.2.0"
[dependencies]
regex = { workspace = true, features = ["unicode"] }
[build-dependencies]
cc.workspace = true
[dev-dependencies]
rand.workspace = true
Feature Flag
除了workspace之外,Rust Cargo 还提供了一个称为条件编译或可选依赖项的功能,features。
Cargo “features” provide a mechanism to express conditional compilation and optional dependencies. A package defines a set of named features in the [features] table of Cargo.toml, and each feature can either be enabled or disabled
Source: https://doc.rust-lang.org/cargo/reference/features.html我尝试过这个功能并且很喜欢它。我们可以创建一个条件块代码,如果我们在将其定义为依赖项时激活它,则该代码将作为“功能”启用,类似 C/C++中的宏控制,例如: 在Cargo.toml:
[features]
# Defines a feature named `webp` that does not enable any other features.
webp = []
每个“标志”也可以依赖于其他“标志”,例如:
[features]
bmp = []
png = []
ico = ["bmp", "png"]
webp = []
可选依赖项示例:
[dependencies]
gif = { version = "0.11.1", optional = true }
[features]
gif = ["dep:gif"]
这意味着仅当启用了 gif 功能时才会包含此依赖项。这种简单的操作但非常有用。如果我们正在构建大型代码库或项目或大型应用程序,此功能将帮助我们减少编译二进制文件的大小,因为我们能够根据我们的特定需求选择正确的依赖项。
总结
对我来说,cargo 不仅仅是一个仅设置库依赖项的包管理器,而且还不止于此。
- 它确实帮助我们维护大型代码库(使用工作空间)
- 它帮助我们维护条件依赖
- 使用 TOML 格式提供干净、简单的配置,无需学习新的自定义 DSL 或任何奇葩脚本(如 makefile )
- 它已经为我们提供了管理代码库布局的标准约定,不再有固执己见的布局。
- 还有很多其他的事情
希望我的文章可以帮助一些决定学习或使用 Rust 作为技术栈的你。