参考: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 --release
的profile.release
配置
当项目的 Cargo.toml 文件中没有任何 [profile.*]
部分的时候,Cargo 会对每一个配置都采用默认设置。通过增加任何希望定制的配置对应的 [profile.*]
部分,我们可以选择覆盖任意默认设置的子集。例如,如下是 dev
和 release
配置的 opt-level
设置的默认值:
# Cargo.toml
[profile.dev]
opt-level = 0 # 降低性能优化级别,缩短编译时间,但是二进制程序的运行时间更长
[profile.release]
opt-level = 3 # 提高性能优化级别,缩短二进制程序运行时间,但是增加了编译时间
opt-level
设置控制 Rust 会对代码进行何种程度的优化。这个配置的值从 0 到 3。
越高的优化级别需要更多的时间编译,所以如果你在进行开发并经常编译,可能会希望在牺牲一些代码性能的情况下编译得快一些。这就是为什么 dev
的 opt-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
/// Adds one to the number given.
///
/// # Examples
///
///
/// let arg = 5; /// let answer = my_crate::add_one(arg); /// /// assert_eq!(6, answer); /// ``` pub fn add_one(x: i32) -> i32 { x + 1 }
使用 `cargo doc --open` 命令生成并打开 HTML 文档:project 是你的 package name
```bash
$ cargo doc --open
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Opening /../project/target/doc/project/index.html
如果在服务器端生成文档,可以利用 http 服务来公网访问文档:
$ cargo doc
$ cp -r target/doc/ /var/www/html/
把文档复制/移动至 /var/www/html/
下,本地浏览器访问:http://你的ip/doc/project/index.html
常用的文档注释会包括的内容:
- Examples:使用例子,对使用者非常有帮助。
- Panics:这个函数可能会 panic! 的场景。并不希望程序崩溃的函数调用者应该确保他们不会在这些情况下调用此函数。
- Errors:如果这个函数返回 Result,此部分描述可能会出现何种错误以及什么情况会造成这些错误,这有助于调用者编写代码来采用不同的方式处理不同的错误。
Safety:如果这个函数使用 unsafe 代码,这一部分应该会涉及到期望函数调用者支持的确保 unsafe 块中代码正常工作的不变条件(invariants)。
大部分文档注释不需要所有这些部分,不过这是一个提醒你检查调用你代码的人有兴趣了解的内容的列表。。\\!
用于说明结构如果仅仅对 item 注释,就像一篇文章只有段落和句子,没有结构,所以需要总述性的注释,这就是
\\!
的作用。
这种结构是很宽泛的 —— mod 内部所有的 item 形成结构,与外部代码隔离开的代码放在一起的意义在于它们是很强相关的;impl 块内部也是相关的结构,因为里面的方法/关联函数都为一种类型服务。\\!
位于模块文件顶部 或者 模块声明的下一行,不能放在任何一个 item 前面。因为它不是对一个 item 说明,而是对结构内所有的 item 进行说明。src/lib.rs 文件内容:
//! # outline
//!
//! `rust_book_coding_in_practice` is a demo crate written during
//! the early stage of learning Rust.
//!
//! And it may be organized not so well.
//!
//! Anyway, just keep moving forward!
pub mod test;
src/lib.rs 的
//!
注释是顶级的模块注释,下面 crate doc 首页的 outline 内容就是从这里生成的:src/test.rs 文件内容:
//! give your moduel a breif description
//!
//! # purpose
//!
//! do the fundmental calculation
/// Adds one to the number given.
///
/// # Examples
///
///
/// 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 }
src/test.rs 在 lib.rs 中被定义为公开的模块。而进入模块文档之后看到的一行说明和 purpose 内容就是在 src/test.rs 顶部的 `//!` 注释,而且首页 test 这个模块文档的链接入口旁的一行说明就是 `//!` 注释的的一行文字。(这里的一行指 md 的行,md 换行需要一行空白行,而不是直接文本换行)<br /><br />进入 functions 下的 add_one 文档,最上面有自动生成的函数签名,下面就是 `///` 文档注释的地方。<br /><br />`//!` 注释也常用在 impl 块中:
```rust
/// just one field and its value are no negetive integer
pub struct I {
one: u32,
}
impl I {
//! here are the methods that are implemented to struct `I`
//!
//! # Description
//! blabla
//!
//! # Examples
//! blabla
/// here is the description only about method `new`
/// (associated function exactly speaking)
pub fn new() -> Self { Self { one: 1 } }
/// here is the description and usage of the method
pub fn add_one(self) -> u32 { self.one + 1 }
}
rustdoc
///
和//!
的成果以
rand
crate 的 文档 首页为参照,一个完整 crate doc 细节剖析:
关于 rustdoc 更深入的参考链接:
- cargo doc 命令参数:https://doc.rust-lang.org/cargo/commands/cargo-doc.html
- rustdoc 文档:https://doc.rust-lang.org/rustdoc/index.html
- 导入 md 文档来注释代码:https://doc.rust-lang.org/unstable-book/language-features/external-doc.html
mdbook:类似 rustdoc 功能,使用 markdown 构建电子书(我们看到的 Rust Book 就是它实现的): https://github.com/rust-lang/mdBook
pub use
重导出使用
pub use
重导出的 item 会被单独放到 doc 首页,并且显示其完整路径。而在实际使用时,重导出的 item 只要use outer_crate::重导出的item
即可。//! # Art
//!
//! A library for modeling artistic concepts.
pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;
pub mod kinds {
// --snip--
}
pub mod utils {
// --snip--
}
crates.io
crates.io 用来分发包的源代码,它主要托管开源代码。你可以通过它发布自己的包来向它人分享代码。
创建并登陆帐号:去 https://crates.io/me/ 用 github 账户登陆,并获取 API token。在本地用
$ cargo login abcdefghijklmnopqrstuvwxyz012345
$ cargo login # 或者密文输入
或者编辑 ~/.cargo/credentials 文件,添加:
[registry]
token = "token"
在待发布的 crate 中添加元信息 (metadata):
[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0" # `OR` 表明多重许可
[dependencies]
发布 crate:
$ cargo publish
发布 crate 时请多加小心,因为发布是 永久性的(permanent)。对应版本不可能被覆盖,其代码也不可能被删除。crates.io 的一个主要目标是作为一个存储代码的永久文档服务器,这样所有依赖 crates.io 中的 crate 的项目都能一直正常工作。而允许删除版本没办法达成这个目标。
- 更新 crate 时,使用 语义化版本规则 来根据修改的类型决定下一个版本号。接着运行
cargo publish
来上传新版本。 - 撤回 (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) 的理由:随着项目增长
- lib crate 持续增大,而你希望将其进一步拆分成多个 lib crate(因为一个 package 最多一个 lib crate+多个 binary crate)
- 每一个更小的组件比一大块代码要容易理解。如果它们经常需要同时被修改的话,,工作空间可以帮助我们管理多个相关的协同开发的 packages / crates。
Rust 中,项目最庞大的项目就是其自身,参考其源代码中如何同时构建 packages:https://github.com/rust-lang/rust,尤其关注里面每个 Cargo.toml
文件如何配置。
MWE ( minimal working example):
创建 workspace root,也就是工作空间主目录:
$ mkdir add
$ cd add
声明项目空间成员和进行其他配置:
# Cargo.toml
[workspace]
members = [
"adder", # 支持多级文件夹,比如 "caculate/adder"
"add-one", # 注意 导入含 `-` 的 crate 时需要使用下划线: use add_one
]
初始化 binary 或者 lib crate,其名称与声明时的对应上
$ cargo new adder
$ cargo new add-one --lib
在 member (packages) 里面按照正常方式编写代码,即只允许一个 lib 和多个 binary
# adder/Cargo.toml
# default contents
[dependencies]
# 添加本地同一个项目空间 crate 依赖,用路径指定
add-one = { path = "../add-one" }
# 添加外部 crate 时,members 之间有如果有共同依赖
# 需要显式在各自 package 的 Cargo.toml 文件声明
# 而实际 workspace 会确保共同的外部依赖采用相同的版本
# 而且同时使用一份依赖,不会额外拷贝依赖以节省空间
// adder/src/main.rs
use add_one;
fn main() {
let num = 10;
println!(
"Hello, world! {} plus one is {}!",
num,
add_one::add_one(num)
);
}
// add-one/src/lib.rs
pub fn add_one(x: i32) -> i32 { x + 1 }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(3, add_one(2));
}
}
cargo 命令有些变化:
cargo build/test
会编译/测试所有 members 的 binary/lib crates-p
参数和 package 名称用来指定工作空间中我们希望使用的包,比如cargo run -p adder
、cargo test -p add-one
cargo publish
命令并没有--all
或者-p
参数,所以必须进入每一个 crate 的目录并运行cargo publish
来发布工作空间中的每一个 crate。
MWE 工作空间最终的目录结构:
.
├── add-one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── Cargo.lock
├── Cargo.toml
└── target
├── CACHEDIR.TAG
└── debug
├── adder
├── adder.d
├── build
├── deps
├── examples
├── incremental
├── libadd_one.d
└── 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 —— 目前最快的本地搜索工具:
$ cargo install ripgrep
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading ripgrep v0.3.2
--snip--
Compiling ripgrep v0.3.2
Finished release [optimized + debuginfo] target(s) in 97.91 secs
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
.
├── 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