【Rust 学习记录】14. 进一步认识Cargo及crates.io

TwoSix Lv3

这一章就主要讲讲Cargo这个工具的一些用途,主要是以下几个部分,没有涉猎到的可以在官网 查看cargo更全面的介绍:

  1. build时使用的release profile相关介绍
  2. 怎么将你写的包发布到creates.io上给别人用
  3. 使用工作空间组织项目
  4. 下载安装craetes.io的包
  5. 使用自定义命令来扩展cargo

release profile

Rust内置了两套release profile,一套就是我们在cargo build时候使用的debug用dev profile,一套就是cargo build --release对应的发布用release profile

我们可以在toml文件内自定义的修改这两套配置。对应的在[profile.xxx ]字段内,如下:

1
2
3
4
5
[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

opt-level对应的是编译时的优化等级,等级越高,编译耗时越长,但最终可执行文件的运行速度就越快,值只能是0,1,2,3。其中dev配置默认是0,relese配置默认是3。如果有必要,你也可以自行选择修改成1和2

怎么发布你的包到creates.io

编写有用的文档注释

一份好的文档能帮助用户快速理解这个包的具体用途和使用方法,Rust提供了一种可以快速生成HTML文档的注释方法(nb,我还以为是docstring,居然可以直接生成文档)

之前我们都知道双斜杠//可以注释,而我们的文档注释就是///三斜杠,文档注释主要用来介绍使用方法,而不是介绍包的实现细节,例如:

lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
/// 将传入的数字加1
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}

第一行介绍了函数对应的功能,然后用类似markdown的语法表明了一块Examples区域,最后提供了一片代码块示例。写完之后,我们只需要运行命令:cargo doc就可以自动生成一份HTML文档了,再执行cargo doc --open就可以自动用浏览器打开文档了。

生成后的样子:
img

除了Examples外,还有其他一些可选的文档区域,如:

  • Panics,指出函数可能引发panic的场景。不想触发panic的调用者应当确保自己的代码不会在这些场景下调用该函数。
  • Errors,当函数返回Result作为结果时,这个区域会指出可能出现的错误,以及造成这些错误的具体原因,它可以帮助调用者在编写代码时为不同的错误采取不同的措施。
  • Safety,当函数使用了unsafe关键字(在第19章讨论)时,这个区域会指出当前函数不安全的原因,以及调用者应当确保的使用前提。

此外,有意思的是,你写的示例内的代码并不只是给用户看的。在你执行cargo test的时候,cargo会把你文档注释内的代码一并执行测试,因为没有比文档内的示例代码不能正常执行更让人恶心的了哈哈哈。


另一种文档注释的形式是//!。这种文档注释会显示在注释的最外层,一般用在包的根文件上,用来介绍整个包的大致情况。我们改成如下注释看看:

lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//! # My Crate
//!
//! my_crate是一系列工具的集合,
//! 这些工具被用来简化特定的计算操作

/// 将传入的数字加1
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}

此时生成的文档如下:

img

可以看见//!的注释显示在了add_one对应注释的外层,点击add_one后才会进入刚才对应的add_one用例文档

使用pub use来导出合适的公共API

这一部分的内容和use章里讲的内容基本是异曲同工,不过这里可以结合文档部分再讲一下。

这一部分解决的问题主要是:假如我们的代码结构嵌套的非常复杂的时候,那么用户在调用的时候代码就会写成use::aaa::bbb::ccc::ddd::eee::fff,很长很难看也很不好用,那该怎么办呢?

结合文档来看一下,例如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//! # Art
//!
//! 一个用来建模艺术概念的代码库

pub mod kinds {
/// RYB颜色模型的三原色
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}

/// RYB模型的调和色
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}

pub mod utils {
use crate::kinds::*;

/// 将两种等量的原色混合生成调和色
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// --略--
}
}

会生成以下文档:

img

可见暴露在外的只有kinds和utils,而我们高频使用的应该是kinds和utils的内容,这部分却要点进二级目录才能看到具体有什么。

要解决这个问题,我们只需要在结构外use一遍,把里面的结构暴露到外面即可。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//! # Art
//!
//! 一个用来建模艺术概念的代码库

pub use self::kinds::PrimaryColor;
pub use self::kinds::SecondaryColor;
pub use self::utils::mix;

pub mod kinds {
/// RYB颜色模型的三原色
pub enum PrimaryColor {
Red,
Yellow,
Blue,
}

/// RYB模型的调和色
pub enum SecondaryColor {
Orange,
Green,
Purple,
}
}

pub mod utils {
use crate::kinds::*;

/// 将两种等量的原色混合生成调和色
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// --略--
}
}

以上代码我们只是在开头添加了几句pub use,生成的文档如下

img

可见我们pub use的函数也暴露在文档首页了;不过具体注释还是要点进去才能看见

同时,我们在文件外使用这些函数的时候也从:

1
2
3
pub use art::kinds::PrimaryColor;
pub use art::kinds::SecondaryColor;
pub use art::utils::mix;

变成了:

1
2
3
pub use art::PrimaryColor;
pub use art::SecondaryColor;
pub use art::mix;

发布你的包

代码完成后,发布包只需按照以下几个步骤

  1. 创建crates.io账户

    创建省略

    创建完毕后需要获取api令牌,并使用cargo进行本地身份验证,如:

    1
    cargo login abcdefghijklmnopqrstuvwxyz012345

    这句命令会把你的令牌存入~/.cargo/credentials

  2. 为包添加元数据

    Cargo.toml里面可以配置包名,描述,版本等等信息,填写示例如下:

    1
    2
    3
    4
    5
    6
    7
    [package]
    name = "guessing_game"
    version = "0.1.0"
    authors = ["Your Name <[email protected]>"]
    edition = "2018"
    description = "A fun game where you guess what number the computer has chosen."
    license = "MIT OR Apache-2.0"
  3. 发布:执行cargo publish即可完成发布

  4. 更新:只需要更改toml里的version字段,再重新publish就能完成新版本的更新了

  5. 撤回:执行cargo yank --vers 1.0.1即可撤回指定版本的包;如果操作失误了,也可以撤回你的撤回:cargo yank --vers 1.0.1 --undo;但需要注意的是,撤回不会删除任何代码,只能阻止别人的代码下载或指定这个版本的包为依赖

Cargo工作空间

工作空间的概念可以把项目的代码划分成若干个包,在互相关联的同时更方便的协同开发。

创建工作空间

这时候我们需要新建一个文件夹,名为add,直接新建一个文件夹即可,不需要cargo new命令创建。

然后,我们再自己建一个Cargo.toml文件,填入以下内容

1
2
3
4
5
[workspace]

members = [
"adder",
]

这个toml文件不像我们之前碰到的toml一样有一堆字段,他只包含一个工作空间字段,声明了工作空间下的成员有一个adder,这个adder我们还没创建,现在来创建一下。

使用cargo new字段创建一个二进制包:cargo new adder

这个时候我们就已经可以在add目录下使用cargo build构建整个工作空间了。

在工作空间里创建第二个包

我们可以用cargo new add-one --lib来创建一个代码包,当然,我们同时也得在toml文件加上

1
2
3
[workspace]

members = ["adder", "add-one"]

然后,我们再在add-one里的lib.rs写上我们可以供外部调用的接口函数,如下:

1
2
3
pub fn add_one(x: i32) -> i32 {
x + 1
}

那我们在adder里要怎么调用add-one里的接口呢?

工作空间是不会自动为你假设出依赖关系的,所以需要自己在toml里显式的指定,例如我们要在adder里调用add-one的函数,就要在adder的toml里添加以下字段

1
2
3
[dependencies]

add-one = { path = "../add-one" }

这一部分指定了adder需要依赖的包,名字是什么,对应的包路径在哪。显式指定后,我们就可以在addermain.rs里编写代码,并使用add-one里的接口了,如下:

1
2
3
4
5
6
use add_one;

fn main() {
let num = 10;
println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
}

最后,我们再在根目录add下build编译并运行。

运行和往常有所不同,因为一个工作空间可能有很多个二进制包,所以我们需要指定运行哪个二进制包,加上-p参数即可,如:cargo run -p adder

然后就可以看到正常的输出了

在工作空间中依赖外部包

和以前一样,也是在toml里指定即可,例如这里我们给add-one引入一个rand包,如下:

1
2
3
[dependencies]

rand = "0.3.14"

再执行cargo build,就可以自动的下载安装rand包了。


这里值得一提的是,Cargo.lock文件只在add根目录下有,这是为了保证工作空间下所有包的依赖都是同一版本的,一是节约了磁盘空间,二是确保工作空间下的包互相之间是兼容的,避免奇奇怪怪的问题。

工作空间下进行测试

和往常一样编写测试代码,然后在根目录下执行cargo test即可自动完成所有测试。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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 test
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
Compiling add-one v0.1.0 (E:\Code\rust\learning\add\add-one)
Compiling adder v0.1.0 (E:\Code\rust\learning\add\adder)
Finished test [unoptimized + debuginfo] target(s) in 0.29s
Running unittests src\lib.rs (target\debug\deps\add_one-48bf46fc7c463809.exe)

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

Running unittests src\main.rs (target\debug\deps\adder-174daa247398ed5b.exe)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Doc-tests add-one

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

使用cargo install来安装可执行程序

cargo install的主要作用是用来安装别人分享的二进制包的,可以直接在命令行使用别人写好的工具。安装会装在rust安装的根目录下的bin文件夹里,所以如果要直接使用的话,需要配置一下环境变量,把这个目录加进去。

例如:cargo install ripgrep

具体就没什么好演示的了,这是一个用来搜索文件的小工具。

使用自定义命令扩展Cargo的功能

cargo xxx可以运行xxx这个二进制文件,所以如果你用cargo install安装了某个扩展,也可以用cargo xxx来运行

  • 标题: 【Rust 学习记录】14. 进一步认识Cargo及crates.io
  • 作者: TwoSix
  • 创建于 : 2024-01-23 21:55:47
  • 更新于 : 2024-07-04 23:52:28
  • 链接: https://twosix.page/2024/01/23/【Rust-学习记录】14-进一步认识Cargo及crates-io/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论