参考:https://kaisery.github.io/trpl-zh-cn/ch14-00-more-about-cargo.html
Cargo 的功能不止本章所介绍的,关于其全部功能的详尽解释,请查看 cargo 文档rust-by-example/cargo

[profile.*] 自定义构建配置

profiles:是预定义的、可定制的带有不同选项的配置,他们允许程序员更灵活地控制代码编译的多种选项。每一个配置都彼此相互独立。
Cargo 有两个主要的配置:

  • 运行 cargo build 时采用的 profile.dev 配置
  • 运行 cargo build --releaseprofile.release 配置

当项目的 Cargo.toml 文件中没有任何 [profile.*] 部分的时候,Cargo 会对每一个配置都采用默认设置。通过增加任何希望定制的配置对应的 [profile.*] 部分,我们可以选择覆盖任意默认设置的子集。例如,如下是 devrelease 配置的 opt-level 设置的默认值:

  1. # Cargo.toml
  2. [profile.dev]
  3. opt-level = 0 # 降低性能优化级别,缩短编译时间,但是二进制程序的运行时间更长
  4. [profile.release]
  5. opt-level = 3 # 提高性能优化级别,缩短二进制程序运行时间,但是增加了编译时间

opt-level 设置控制 Rust 会对代码进行何种程度的优化。这个配置的值从 0 到 3。
越高的优化级别需要更多的时间编译,所以如果你在进行开发并经常编译,可能会希望在牺牲一些代码性能的情况下编译得快一些。这就是为什么 devopt-level 默认为 0。当你准备发布时,花费更多时间在编译上则更好。只需要在发布模式编译一次,而编译出来的程序则会运行很多次,所以发布模式用更长的编译时间换取运行更快的代码。这正是为什么 release 配置的 opt-level 默认为 3

文档注释

文档注释documentation comments ):用于生成 HTML 文档的代码注释。这些 HTML 展示公有 API 文档注释的内容,他们意在让对库感兴趣的程序员理解如何 使用 这个 crate,而不是它是如何被 实现 的。生成 HTML 文档的命令为 cargo doc

/// 用于说明 item

文档注释使用三斜杠 /// 而不是两斜杠 // ,支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项 之前。

item (whatever is defined meanwhile public): pub fn | struct | enum | trait | mod | const

  1. /// Adds one to the number given.
  2. ///
  3. /// # Examples
  4. ///
  5. ///

/// let arg = 5; /// let answer = my_crate::add_one(arg); /// /// assert_eq!(6, answer); /// ``` pub fn add_one(x: i32) -> i32 { x + 1 }

  1. 使用 `cargo doc --open` 命令生成并打开 HTML 文档:project 是你的 package name
  2. ```bash
  3. $ cargo doc --open
  4. Finished dev [unoptimized + debuginfo] target(s) in 0.01s
  5. Opening /../project/target/doc/project/index.html

如果在服务器端生成文档,可以利用 http 服务来公网访问文档:

  1. $ cargo doc
  2. $ cp -r target/doc/ /var/www/html/

把文档复制/移动至 /var/www/html/ 下,本地浏览器访问:http://你的ip/doc/project/index.html
image.png
常用的文档注释会包括的内容:

  • Examples:使用例子,对使用者非常有帮助。
  • Panics:这个函数可能会 panic! 的场景。并不希望程序崩溃的函数调用者应该确保他们不会在这些情况下调用此函数。
  • Errors:如果这个函数返回 Result,此部分描述可能会出现何种错误以及什么情况会造成这些错误,这有助于调用者编写代码来采用不同的方式处理不同的错误。
  • Safety:如果这个函数使用 unsafe 代码,这一部分应该会涉及到期望函数调用者支持的确保 unsafe 块中代码正常工作的不变条件(invariants)。
    大部分文档注释不需要所有这些部分,不过这是一个提醒你检查调用你代码的人有兴趣了解的内容的列表。。

    \\! 用于说明结构

    如果仅仅对 item 注释,就像一篇文章只有段落和句子,没有结构,所以需要总述性的注释,这就是 \\! 的作用。
    这种结构是很宽泛的 —— mod 内部所有的 item 形成结构,与外部代码隔离开的代码放在一起的意义在于它们是很强相关的;impl 块内部也是相关的结构,因为里面的方法/关联函数都为一种类型服务。
    \\! 位于模块文件顶部 或者 模块声明的下一行,不能放在任何一个 item 前面。因为它不是对一个 item 说明,而是对结构内所有的 item 进行说明。

  • src/lib.rs 文件内容:

    1. //! # outline
    2. //!
    3. //! `rust_book_coding_in_practice` is a demo crate written during
    4. //! the early stage of learning Rust.
    5. //!
    6. //! And it may be organized not so well.
    7. //!
    8. //! Anyway, just keep moving forward!
    9. pub mod test;

    src/lib.rs 的 //! 注释是顶级的模块注释,下面 crate doc 首页的 outline 内容就是从这里生成的:
    image.png

  • src/test.rs 文件内容:

    1. //! give your moduel a breif description
    2. //!
    3. //! # purpose
    4. //!
    5. //! do the fundmental calculation
    6. /// Adds one to the number given.
    7. ///
    8. /// # Examples
    9. ///
    10. ///

    /// let arg = 5; /// let answer = rust_book_coding_in_practice::add_one(arg); /// /// assert_eq!(6, answer); /// ``` pub fn add_one(x: i32) -> i32 { x + 1 }

    1. src/test.rs lib.rs 中被定义为公开的模块。而进入模块文档之后看到的一行说明和 purpose 内容就是在 src/test.rs 顶部的 `//!` 注释,而且首页 test 这个模块文档的链接入口旁的一行说明就是 `//!` 注释的的一行文字。(这里的一行指 md 的行,md 换行需要一行空白行,而不是直接文本换行)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2697283/1621512403394-65206581-be07-4f43-8173-b799baaee079.png#clientId=u2be77d27-db55-4&from=paste&height=249&id=uda0337cc&name=image.png&originHeight=497&originWidth=1134&originalType=binary&size=75814&status=done&style=none&taskId=u8115c4e2-d26d-4953-b76a-af1aa4cb41c&width=567)<br />进入 functions 下的 add_one 文档,最上面有自动生成的函数签名,下面就是 `///` 文档注释的地方。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2697283/1621512444590-0e4d19e2-275a-4b9a-9b35-0e5faf938399.png#clientId=u2be77d27-db55-4&from=paste&height=268&id=ua120f097&name=image.png&originHeight=535&originWidth=1120&originalType=binary&size=76169&status=done&style=none&taskId=u5bf7b68d-6403-41fb-a3e7-2af02b62f23&width=560)<br />`//!` 注释也常用在 impl 块中:
    2. ```rust
    3. /// just one field and its value are no negetive integer
    4. pub struct I {
    5. one: u32,
    6. }
    7. impl I {
    8. //! here are the methods that are implemented to struct `I`
    9. //!
    10. //! # Description
    11. //! blabla
    12. //!
    13. //! # Examples
    14. //! blabla
    15. /// here is the description only about method `new`
    16. /// (associated function exactly speaking)
    17. pub fn new() -> Self { Self { one: 1 } }
    18. /// here is the description and usage of the method
    19. pub fn add_one(self) -> u32 { self.one + 1 }
    20. }

    生成 doc 结果如下:
    image.png

    rustdoc

    /////! 的成果

    rand crate 的 文档 首页为参照,一个完整 crate doc 细节剖析:
    image.png
    image.png
    关于 rustdoc 更深入的参考链接:

  1. cargo doc 命令参数:https://doc.rust-lang.org/cargo/commands/cargo-doc.html
  2. rustdoc 文档:https://doc.rust-lang.org/rustdoc/index.html
  3. 导入 md 文档来注释代码:https://doc.rust-lang.org/unstable-book/language-features/external-doc.html
  4. mdbook:类似 rustdoc 功能,使用 markdown 构建电子书(我们看到的 Rust Book 就是它实现的): https://github.com/rust-lang/mdBook

    pub use 重导出

    使用 pub use 重导出的 item 会被单独放到 doc 首页,并且显示其完整路径。而在实际使用时,重导出的 item 只要 use outer_crate::重导出的item 即可。

    1. //! # Art
    2. //!
    3. //! A library for modeling artistic concepts.
    4. pub use self::kinds::PrimaryColor;
    5. pub use self::kinds::SecondaryColor;
    6. pub use self::utils::mix;
    7. pub mod kinds {
    8. // --snip--
    9. }
    10. pub mod utils {
    11. // --snip--
    12. }

    15 cargo 命令 - 图6

    crates.io

    crates.io 用来分发包的源代码,它主要托管开源代码。你可以通过它发布自己的包来向它人分享代码。

  5. 创建并登陆帐号:去 https://crates.io/me/ 用 github 账户登陆,并获取 API token。在本地用

    1. $ cargo login abcdefghijklmnopqrstuvwxyz012345
    2. $ cargo login # 或者密文输入
  6. 或者编辑 ~/.cargo/credentials 文件,添加:

    1. [registry]
    2. token = "token"
  7. 在待发布的 crate 中添加元信息 (metadata):

    1. [package]
    2. name = "guessing_game"
    3. version = "0.1.0"
    4. authors = ["Your Name <you@example.com>"]
    5. edition = "2018"
    6. description = "A fun game where you guess what number the computer has chosen."
    7. license = "MIT OR Apache-2.0" # `OR` 表明多重许可
    8. [dependencies]
  8. 发布 crate:

    1. $ cargo publish
  9. 发布 crate 时请多加小心,因为发布是 永久性的(permanent)。对应版本不可能被覆盖,其代码也不可能被删除。crates.io 的一个主要目标是作为一个存储代码的永久文档服务器,这样所有依赖 crates.io 中的 crate 的项目都能一直正常工作。而允许删除版本没办法达成这个目标。

  10. 更新 crate 时,使用 语义化版本规则 来根据修改的类型决定下一个版本号。接着运行 cargo publish 来上传新版本。
  11. 撤回yanking )某个版本:虽然你不能删除之前版本的 crate,但是可以阻止任何将来的项目将他们加入到依赖中。
    这在某个版本因为这样或那样的原因被破坏的情况很有用。撤回某个版本会阻止新项目开始依赖此版本,不过所有现存此依赖的项目仍然能够下载和依赖这个版本。撤回 依然没有 删除任何代码。
    从本质上说,撤回意味着所有带有 Cargo.lock 的项目的依赖不会被破坏,同时任何新生成的 Cargo.lock 将不能使用被撤回的版本。

    工作空间

    工作空间是管理项目的一种方式,实际上就是:
  • 多个 packages 放在同一个 workspace root 下面
  • 所有 package 使用同一个 target 输出目录,且同时被编译、测试、管理依赖
  • 所有 package 共享一个 Cargo.lock,且 Cargo.lock 放置在 workspace root 下面
  • [[patch]](https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section)[[replace]](https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-replace-section)[[profile.*]](85b66dd765607a28d65158ccd230e8f7) 部分只在 workspace root 下的 Cargo.toml 有效; member crates 的 Cargo.toml 里的这三种字段是无效的
  • 发布到 crates.io 时,所有 crates 需要 单独 发布

使用工作空间 (workspaces) 的理由:随着项目增长

  1. lib crate 持续增大,而你希望将其进一步拆分成多个 lib crate(因为一个 package 最多一个 lib crate+多个 binary crate)
  2. 每一个更小的组件比一大块代码要容易理解。如果它们经常需要同时被修改的话,,工作空间可以帮助我们管理多个相关的协同开发的 packages / crates。

Rust 中,项目最庞大的项目就是其自身,参考其源代码中如何同时构建 packages:https://github.com/rust-lang/rust,尤其关注里面每个 Cargo.toml 文件如何配置。
MWE ( minimal working example):

  1. 创建 workspace root,也就是工作空间主目录:

    1. $ mkdir add
    2. $ cd add
  2. 声明项目空间成员和进行其他配置:

    1. # Cargo.toml
    2. [workspace]
    3. members = [
    4. "adder", # 支持多级文件夹,比如 "caculate/adder"
    5. "add-one", # 注意 导入含 `-` 的 crate 时需要使用下划线: use add_one
    6. ]
  3. 初始化 binary 或者 lib crate,其名称与声明时的对应上

    1. $ cargo new adder
    2. $ cargo new add-one --lib
  4. 在 member (packages) 里面按照正常方式编写代码,即只允许一个 lib 和多个 binary

    1. # adder/Cargo.toml
    2. # default contents
    3. [dependencies]
    4. # 添加本地同一个项目空间 crate 依赖,用路径指定
    5. add-one = { path = "../add-one" }
    6. # 添加外部 crate 时,members 之间有如果有共同依赖
    7. # 需要显式在各自 package 的 Cargo.toml 文件声明
    8. # 而实际 workspace 会确保共同的外部依赖采用相同的版本
    9. # 而且同时使用一份依赖,不会额外拷贝依赖以节省空间
    1. // adder/src/main.rs
    2. use add_one;
    3. fn main() {
    4. let num = 10;
    5. println!(
    6. "Hello, world! {} plus one is {}!",
    7. num,
    8. add_one::add_one(num)
    9. );
    10. }
    1. // add-one/src/lib.rs
    2. pub fn add_one(x: i32) -> i32 { x + 1 }
    3. #[cfg(test)]
    4. mod tests {
    5. use super::*;
    6. #[test]
    7. fn it_works() {
    8. assert_eq!(3, add_one(2));
    9. }
    10. }
  5. cargo 命令有些变化:

    • cargo build/test 会编译/测试所有 members 的 binary/lib crates
    • -p 参数和 package 名称用来指定工作空间中我们希望使用的包,比如 cargo run -p addercargo test -p add-one
    • cargo publish 命令并没有 --all 或者 -p 参数,所以必须进入每一个 crate 的目录并运行 cargo publish 来发布工作空间中的每一个 crate。

MWE 工作空间最终的目录结构:

  1. .
  2. ├── add-one
  3. ├── Cargo.toml
  4. └── src
  5. └── lib.rs
  6. ├── adder
  7. ├── Cargo.toml
  8. └── src
  9. └── main.rs
  10. ├── Cargo.lock
  11. ├── Cargo.toml
  12. └── target
  13. ├── CACHEDIR.TAG
  14. └── debug
  15. ├── adder
  16. ├── adder.d
  17. ├── build
  18. ├── deps
  19. ├── examples
  20. ├── incremental
  21. ├── libadd_one.d
  22. └── libadd_one.rlib

cargo 命令

除了常用的 run/build/test/doc 之外的命令。

cargo install

cargo install 命令用于在本地安装和使用二进制 crate。
它并不打算替换系统中的包;它意在作为一个方便 Rust 开发者们安装其他人已经在 crates.io 上共享的工具的手段。只有拥有二进制目标文件的包能够被安装。
也就是 cargo install 能安装 crates.io 上发布了 binary crate 的软件,把 binary crate 源代码下载之后,进行本地编译,最后把可执行文件放置在 .cargo/bin 下。保将这个目录添加到 $PATH 环境变量中就能够运行通过 cargo install 安装的程序了。
例如安装 ripgrep —— 目前最快的本地搜索工具:

  1. $ cargo install ripgrep
  2. Updating registry `https://github.com/rust-lang/crates.io-index`
  3. Downloading ripgrep v0.3.2
  4. --snip--
  5. Compiling ripgrep v0.3.2
  6. Finished release [optimized + debuginfo] target(s) in 97.91 secs
  7. Installing ~/.cargo/bin/rg

最后一行输出展示了安装的二进制文件的位置和名称,在这里 ripgrep 被命名为 rg。只要你像上面提到的那样将安装目录加入 $PATH,就可以运行 rg --help 并开始使用一个更快更 Rust 的工具来搜索文件了!
此外,还可以通过 strip 命令或者 upx 等工具,对生成的执行文件文件“瘦身”,减小体积。

自定义 cargo 子命令

Cargo 的设计使得开发者可以通过新的子命令来对 Cargo 进行扩展,而无需修改 Cargo 本身。
如果 $PATH 中有类似 cargo-something 的二进制文件,就可以通过 cargo something 来像 Cargo 子命令一样运行它。
像这样的自定义命令也可以运行 cargo --list 来展示出来。
能够通过 cargo install 向 Cargo 安装扩展并可以如内建 Cargo 工具那样运行他们是 Cargo 设计上的一个非常方便的优点!

一些常见配置

依赖的语义化控制

参考:eference/specifying-dependencies

build

参考:https://doc.rust-lang.org/cargo/reference/build-scripts.html

常见的项目结构

参考:https://doc.rust-lang.org/cargo/guide/project-layout.html

  1. .
  2. ├── Cargo.lock
  3. ├── Cargo.toml
  4. ├── src/
  5. ├── lib.rs
  6. ├── main.rs
  7. └── bin/
  8. ├── named-executable.rs
  9. ├── another-executable.rs
  10. └── multi-file-executable/
  11. ├── main.rs
  12. └── some_module.rs
  13. ├── benches/
  14. ├── large-input.rs
  15. └── multi-file-bench/
  16. ├── main.rs
  17. └── bench_module.rs
  18. ├── examples/
  19. ├── simple.rs
  20. └── multi-file-example/
  21. ├── main.rs
  22. └── ex_module.rs
  23. └── tests/
  24. ├── some-integration-tests.rs
  25. └── multi-file-test/
  26. ├── main.rs
  27. └── test_module.rs