前言#
昨天我们用iterator优化了我们的minigrep小工具
今天往下学~
深入的了解Cargo以及Crates.io#
目前我们仅是会一些基础的指令,比如run/build/test,我们将在这一章学到一些较深入的cargo相关的知识。
比如:
- 通过发布配置文件(
release profiles)来自定义构建。 - 发布一个库到
crates.io中。 - 使用工作区(
workspaces)来组织大型项目。 - 从
crates.io中下载库。 - 基于
cargo拓展自定义的指令。
Cargo的官方链接:
Introduction - The Cargo Bookdoc.rust-lang.org/cargo/
通过配置文件自定义构建#
release profiles是需要在构建之前配置的,我们可以配置多个文件来使用不同的配置项.
比如前端工程化有dev和prod两种模式.
而在rust中也差不多,有dev和release两种.
当我们运行cargo build的时候就是dev模式,而执行cargo build --release则是release模式.
这俩模式其实cargo已经有一套较好的默认值了,这也就是为什么我们不需要配置cargo.toml就可以执行上面两个指令的原因.

可以看到构建输出的内容是会展示是dev还是release的.
现在我们来看下如何单独配置它们的项
首先找到我们的Cargo.toml文件,所有的配置都是放在这个文件中的,而要单独配置某种mode只需要单独开个sections也就是区域就行了,比如下面这样
[profile.dev]
opt-level = 0
[profile.release]
opt-level = 3
默认配置的opt-level也是这样的
opt-level的范围是0 ~ 3,表示编译器准备采取什么等级来优化代码.
等级越高则相对的编译需要的时间就越长,毕竟需要优化的东西更多.
开发模式我们只是看下效果,并不需要考虑优化之类的,所以给个0就好.
而发布模式由于代码是准备上线了,上线后也不会怎么去改动了,所以只需要编译一次,能优化自然就优化掉.
当然这只是推荐的,如果你想在开发模式就看到最终效果,那你也可以把profile.dev的opt-level改为3.
就介绍到这里,想要什么配置可以看下官方提供的文档
Profiles - The Cargo Bookdoc.rust-lang.org/cargo/reference/profiles.html
发布一个crate到crates.io中#
和我们发布库到npm挺像的.
不过在发布前,我们得有要发布的东西才行.
我们先创建一个项目,就放一些用于计算的方法吧
cd ../
cargo new calc_tool --lib
cd calc_tool在lib.rs中
pub struct Calc {
num: usize,
}
impl Calc {
pub fn add(mut self, right: usize) -> usize {
self.num = self.num + right;
self.num
}
pub fn sub(mut self, right: usize) -> usize {
self.num = self.num - right;
self.num
}
pub fn mul(mut self, right: usize) -> usize {
self.num = self.num * right;
self.num
}
pub fn div(mut self, right: usize) -> usize {
self.num = self.num / right;
self.num
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_works() {
let num = Calc { num: 2 };
let result = num.add(2);
assert_eq!(result, 4);
}
#[test]
fn sub_works() {
let num = Calc { num: 2 };
let result = num.sub(2);
assert_eq!(result, 0);
}
#[test]
fn mul_works() {
let num = Calc { num: 2 };
let result = num.mul(2);
assert_eq!(result, 4);
}
#[test]
fn div_works() {
let num = Calc { num: 2 };
let result = num.div(2);
assert_eq!(result, 1);
}
}然后我们来测试下

也是正常之后我们就可以准备给我们的代码加上注释了
添加文档注释#
一个库中的代码应该是得有文档注释的,比如标准库里的

以args方法为例子,可以看到有一堆的注释,而这些注释是用///三斜杠来标注的.
rust将他们称为: 文档注释,它们可以帮助是用这些个方法的开发者知道这个方法要怎么用,是干嘛的.
而这些注释里的例子也就是cargo test时出现在最底部的那个

是的,注释里的东西也是可以被test感知的.
我们直接来试一下
/// a struct name Calc
/// it have five method:
/// add
/// sub
/// mul
/// div
pub struct Calc {
pub num: usize,
}
impl Calc {
/// # Exmaples
/// ```
/// use calc_tool::Calc;
/// let num = Calc { num: 2 };
/// let result = num.add(2);
/// assert_eq!(result, 4);
/// ```
pub fn add(mut self, right: usize) -> usize {
self.num = self.num + right;
self.num
}
/// # Exmaples
/// ```
/// use calc_tool::Calc;
/// let num = Calc { num: 2 };
/// let result = num.sub(2);
/// assert_eq!(result, 0);
/// ```
pub fn sub(mut self, right: usize) -> usize {
self.num = self.num - right;
self.num
}
/// # Exmaples
/// ```
/// use calc_tool::Calc;
/// let num = Calc { num: 2 };
/// let result = num.mul(2);
/// assert_eq!(result, 4);
/// ```
pub fn mul(mut self, right: usize) -> usize {
self.num = self.num * right;
self.num
}
/// # Exmaples
/// ```
/// use calc_tool::Calc;
/// let num = Calc { num: 2 };
/// let result = num.div(2);
/// assert_eq!(result, 1);
/// ```
pub fn div(mut self, right: usize) -> usize {
self.num = self.num / right;
self.num
}
}
然后我们可以运行cargo doc --open将文档以html的形式输出到浏览器中

是不是非常的方便~
实际上cargo是创建了一个target/doc的文件夹

这个文档注释是支持markdown的语法的,所以为了好看些可以加一些markdown的东西,来调整下结构.
另外官方还提供了几个sections用来区分场景,比如我们前面写的# Exmaples
# Panics: 这个方法可能会引起panic,如果你想确保你的代码不会因为这个方法而panic,那么请在xxx场景下不要使用该方法# Errors: 这个方法返回的是一个Result的类型,而这个E在什么场景下是什么类型,什么情况下会出现# Safety: 如果这个方法是unsafe的,那么就需要说清楚为什么是不安全的,以及提供替代方案.
大部分情况下我们都不会使用,不过有需要时还是尽量加上,因为对于用户来说体验感会好很多
另外我们还可以用//!在文件头注释这个crate里有哪些module,毕竟在入口就有指引是一件很舒服的事情.
//! # Calc
//! a struct name Calc
//! it have five method:
//! add
//! sub
//! mul
//! div
pub struct Calc {
pub num: usize,
} 
不过需要注意//!注释得和代码之间隔一个空行.
重写导出路径#
先不说rust,说一个开发库的常态.
以前端为例子
我们开发者开发一个库,为了可维护性,我们会把这个库里的文件按各种不同点拆分,拆分成十几个文件等.
这对于开发者来说可维护性就上去了, 但是对于用代码的人来说就是个灾难: 入口有好几个, 方法一个套一个.
这也就是为什么大多数库都以一个index文件作为入口, 其实都是为了使用者可以方便的使用.
他们一般会把各种入口都import到index文件中,然后export出去.
回到rust中,想一下如果不这么做我们要怎么写,大概率会是这样
lib.rs中
pub mod a {
pub mod b {
pub mod c {
pub fn d () {
println!("test");
}
}
}
}main.rs中
use calc_tool::a::b::c;
c::d(); 很冗长,所以rust团队提供了重导的方式,关键词pub use
lib.rs中
pub use a::b::c;main.rs中
use calc_tool::c;
c::d();这样就简写了很多.
另外这一点也是会暴露在文档里的

我们可以直接点击c进入c这个mod中.
配置Cargo.toml文件#
和配置package.json差不多,在发布前,我们得配置下这个库的名字作者以及开源协议等.
[package]
name = "calc_tool"
version = "0.1.0"
edition = "2021"
description = "a tool to calc"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]具体有哪些metadata可以看这里: Introduction - The Cargo Book
创建账号#
既然我们代码有了,注释也有了,配置文件也配置完了并且测试正常,那这个时候就可以准备发布了.
不过在这之前,我们得有一个自己的账号.
我们用github账号登录即可.
然后配置下自己的邮箱
https://crates.io/settings/email-notifications
验证完邮箱之后就可以拿我们的token了
https://crates.io/settings/tokens
拿到token之后我们到终端登陆下
cargo login xxxxxxxxx当然,如果你的crates.io没有更新会先帮你更新,记得换源或者登梯子.
发布到crates.io上#
登录成功后我们还是不要急着发布.
我们先到crates.io官网看下我们的包是否重名了

并没有,那么就可以放心发布了
cargo publish不过我还是遇到了一个错误

我的代码中有改动没有同步到git上,但是我这个项目压根没有git仓库,所以我直接--allow-dirty跳过了

然后到官网搜索一下

这样就成功啦~
更新库#
如果到时候需要更新,直接改下Cargo.toml里的version然后重新cargo publish即可.
废弃库#
rust并不允许我们直接把库给废弃删除,我们只能废弃之前的版本,这样其它开发者就不会去使用这个版本.
我们直接废弃到我们目前的calc_tool,由于这个包只是个demo,所以并不打算要.
cargo yank --vers 0.1,0 

可以看到被0.1.0废弃了
如果反悔了,可以在终端输入
cargo yank --vers 0.1,0 --undo这样就会恢复了
Cargo Workspaces#
一些大型的框架都会有多个子包比如vue的core[12]包,这是一种国外很火的概念,叫monorepo,就是多包管理.前端可以使用yarn + lerna[13]或者pnpm[14]来管理.
而在rust中,Cargo提供了管理多包的方式,叫workspaces.
创建一个workspaces#
我们先创建一个add文件夹
cd ../
mkdir add
cd add 然后我们在add文件夹中直接创建一个Cargo.toml文件
[workspace]
members = [
"adder"
]这个Cargo.toml将作用于整个包
然后我们创建一个member
cargo new adder 创建完之后我们直接终端输入cargo build

可以看到adder里并没有target,即使是跑到adder里面去执行build指令也是只有add里有target.
为什么要统一输出呢?因为这些个包之间大概率是存在关联引用关系的,如果每个包里都有自己的target,那么就有可能存在重复打包的问题.
在workspace中创建第二个包#
先改下add/Cargo.toml
[workspace]
members = [
"adder",
"add_one",
]然后终端
cargo new add_one --lib我们创建了一个library crate

现在的结构变成了这样
然后我们给add_one加点料之后在adder里面引用它
pub fn add_one(x: i32) -> i32 {
x + 1
}接着要在adder里实现引用它
我们先去到adder/Cargo.toml中,add_one此时对于adder来说就是一个library crate,和通过crates.io引用的其实没什么差别
[package]
name = "adder"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
add_one = { path = "../add_one" }
然后我们就可以在main.rs中使用add_one这个方法了(当然得先use)
use add_one;
fn main() {
let res = add_one::add_one(1);
println!("the result of 1 add 1 is: {res}");
}
终端正常输出
当然,如果你只想输出adder这个binary crate包的内容,可以使用cargo run -p adder,用p指令来指定某一个binary crates编译运行.
引入外部包#
还记得我们的目录么?
我们只有一个Cargo.lock文件,这也就是意味着所有外部引入的包都会共用同一个版本.
这样做可以避免多个版本带来的兼容性问题.
我们来试下把rand这个crate引入到add_one或者adder里面
[dependencies]
add_one = { path = "../add_one" }
rand = "0.8.5" 然后我们在文件中使用
use add_one;
use rand::{self, Rng};
fn main() {
let r = rand::thread_rng().gen_range(0..5);
println!("{r}");
let res = add_one::add_one(1);
println!("the result of 1 add 1 is: {res}");
}接着cargo run一下

也是没问题,来看下Cargo.lock文件中的记录

不过有一点需要注意,那就是只有在其中一个包中引入了才能使用这个包.
比如我们在adder的包中引入了,但是没有在add_one中引入,这时如果在add_one中直接使用就会报错.
所以如果想在add_one中使用,那么也需要在add_one/Cargo.toml文件中引入才行.
至于直接加在全局的Cargo.toml这样的操作是不行的,会报错.
添加测试代码#
其实add_one/lib.rs已经有测试代码了,毕竟我们是new add_one --lib的方式创建的.
那么我们直接cargo test,它将把所有包中的测试代码都执行一遍.

当然,如果你只是想测试某个包中的测试代码,也可以用p指令指明是哪个包,这里就不演示了,和上面cargo run -p xxx的用法一致.
这个指令同样适用于发布creates的时候,需要注意发布的时候要每个包单独发布才行.
使用install指令下载binary crates#
库我们试过了,准确来说是依赖,我们可以通过Cargo.toml文件中[dependencies]引入.
而binary crates我们就没遇到过了.
其实binary crates并不是用在我们的代码中的.
比如ripgrep[20],它只是一个工具,可以用来匹配而已.

我们来下载下,下载完之后默认是放到user/.cargo/bin里面

可以看到是正常下载了.
基于Cargo拓展自定义指令#
其实和npx一样,你可以直接通过package.json中配置bin选项来自定义指令.
官方没有给个例子....
所以这里只能简单的说下
如果你通过install下载的binary crates命名是cargo-xxx,那么你就可以直接在终端cargo xxx跑这个指令调动这个binary.
比如


然后我们直接cargo --list查找当前可用指令,发现这个cargo-miri可以直接使用cargo miri来调用.
总结#
今天的量也是挺多的,不过并不是晦涩难懂的特性概念等.
发布一个自己的包还是很有趣很有成就的.
参考#
- ^rust-14-more-about-cargo-and-crates.io https://doc.rust-lang.org/book/ch14-00-more-about-cargo.html
- ^rust-14.1-customizing-builds-with-release-profiles https://doc.rust-lang.org/book/ch14-01-release-profiles.html#customizing-builds-with-release-profiles
- ^rust-publish-a-crate-to-crates.io https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#publishing-a-crate-to-cratesio
- ^rust-make-useful-document-comment https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#making-useful-documentation-comments
- ^rust-14.2-exporting-a-convenient-public-api-with-pub-use https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#exporting-a-convenient-public-api-with-pub-use
- ^rust-adding-metadata-to-a-new-crate https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#adding-metadata-to-a-new-crate
- ^rust-setting-up-crates.io-account https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#setting-up-a-cratesio-account
- ^rust-publishing-to-crates.io https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#publishing-to-cratesio
- ^rust-updating-crates https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#publishing-a-new-version-of-an-existing-crate
- ^rust-deprecating-version https://doc.rust-lang.org/book/ch14-02-publishing-to-crates-io.html#deprecating-versions-from-cratesio-with-cargo-yank
- ^rust-Cargo-Workspaces https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#cargo-workspaces
- ^vue-core https://github.com/vuejs/core
- ^lerna http://www.febeacon.com/lerna-docs-zh-cn/routes/basic/concepts.html
- ^pnpm https://github.com/pnpm/pnpm
- ^rust-creating-a-workspaces https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#creating-a-workspace
- ^rust-creating-second-crates-in-workspace https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#creating-the-second-package-in-the-workspace
- ^rust-depending-on-external-package-in-workspace https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#depending-on-an-external-package-in-a-workspace
- ^rust-adding-test-to-workspace https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#adding-a-test-to-a-workspace
- ^rust-install-binary-with-cargo-install https://doc.rust-lang.org/book/ch14-04-installing-binaries.html
- ^rust-ripgrep https://crates.io/crates/ripgrep
- ^rust-extending-cargo-with-custom-commands https://doc.rust-lang.org/book/ch14-05-extending-cargo.html#extending-cargo-with-custom-commands
发布于 2022-12-29 18:01・IP 属地广东
