我为什么喜欢 Rust Cargo

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 作为技术栈的你。

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 2
收藏 3
关注 19
成为作者 赚取收益
全部留言
0/200
成为第一个和作者交流的人吧