425 - 《读书笔记:Code Like a Pro in Rust》
周末翻完的,适合有一定 Rust 基础的同学,可以对 Rust 知识进行查漏补缺。纯笔记,未整理。
附:中英双语版,基于 ChatGPT 4 Turbo 翻译,Token 量还真不少。见 https://drive.google.com/file/d/1mOn1q7OLCKO3hpMP-MFogqzh5NCy7c3w/view?usp=drive_link 。
Chapter 02
1、channel。开发有时会需要在不同的 channel 之间切换。
# Runs tests with stable channel:
$ cargo +stable test
...
# Runs tests with nightly channel:
$ cargo +nightly test
为项目指定 channel,可以通过 override 命令实现。此时,其配置在 ~/.rustup/settings.toml
中可以找到。
# Only applies to the current directory and its children
$ rustup override set nightly
也可以在项目里用 rust-toolchain.toml
配(个人更推荐)。
[toolchain]
profile = "default"
channel = "nightly-2023-10-24"
2、Feature flags。
1)可以配置可选依赖
2)feature 会向下传递
3、给依赖打补丁。
如果上游不合,可以指定 git 仓库的 rev。
[dependencies]
num_cpus = { git = "https://github.com/brndnmtthws/num_cpus",
rev = "b423db0a698b035914ae1fd6b7ce5d2a4e727b46" }
遇到间接依赖,可以通过 patch.crates-io
替换所有依赖。
[patch.crates-io]
libc = { git = "https://github.com/rust-lang/libc", tag = "0.2.88" }
4、利用 CI&CD 自动发布 crate。
。
5、链接到 C 库。
6、二进制分发。
交叉编译。
rustup target list
rustup target add aarch64-apple-darwin
cargo build --target aarch64-apple-darwin
7、文档。
生成文档。
rust doc
//!
,crate 或 module 级的文档,在头部///
,对 module、function、trait 或 type 进行注解[func]
链接到函数、模块或其他类型
文档中的示例代码会被作为集成测试执行。
//! # Example
//!
//! -----
//! use rustdoc_example::mult;
//! assert_eq!(mult(10, 10), 100);
//! -----
8、工作区。
以下内容是共享的。
- Cargo.lock
- target/ 输出目录
- 顶层
Cargo.toml
里的[patch]
、[replace]
和[profile.*]
9、自定义构建脚本
。
cargo new aaa --lib
参考:
https://commonmark.org/help/
https://doc.rust-lang.org/stable/rustdoc/
Chapter 03
1、Rust 工具链。
- cargo,项目管理
- rust analyzer,提供 Rust LSP 支持
- rustfmt,Fotmat
- clippy,Lint
- sccache,编译器缓存工具
IntelliJ Rust 不使用 rust analyzer,但与 rust analyzer 共享了一些代码,特别是它的宏支持部分。
rust analyzer 的 Magic Comments 有,
- expr.xxx
- if + match + while
- let + lete + letm
- println + format + logL
- pd 和 ppd
- tfn 和 tmod
Rust 1.58.0 已经支持字符串插值了,比如 foo{foo}
,所以没必要用 format!
了。
参考:
https://rust-analyzer.github.io/manual.html
2、rustfmt
rustup component add rustfmt
cargo fmt
cargo fmt --check -v
cargo fmt --all --check --verbose --package
rustfmt.toml
的推荐配置。
group_imports = "StdExternalCrate"
imports_granularity = "Module"
unstable_features = true
wrap_comments = true
format_code_in_doc_comments = true
version = "Two"
tab_spaces = 2
参考:
https://rust-lang.github.io/rustfmt/?version=v2.0.0-rc.2&search=
3、clippy
clippy 有 450+ 项检查。
rustup component add clippy
cargo clippy
# 修复问题,含 unstable 的
cargo clippy --fix -Z unstable-options
# 出现警告时失败
cargo clippy -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
clippy 类型包括 correctness, restriction, style, deprecated, pedantic, complexity, perf, cargo, and nursery 。
在代码中通过属性禁用规则。
#[allow(clippy::too_many_arguments)]
参考:
https://rust-lang.github.io/rust-clippy/stable/index.html
4、sccache
cargo install sccache
export RUSTC_WRAPPER=`which sccache`
cargo build
sccache 相比 ccache 的优点是,支持网络存储。
export SCCACHE_REDIS=redis://10.10.10.10/sccache
参考:
https://github.com/mozilla/sccache
5、配置 VSCode。
rustup component add rust-src
code --install-extension matklad.rust-analyzer
6、cargo-update
用于更新 cargo install 的包,和 cargo update
不同。
cargo install cargo-update
cargo help install-update
cargo install-update -a
7、cargo-expand
展开宏以查看结果代码。
cargo install cargo-expand
cargo help expand
cargo expand outermod::innermod
8、fuzz
与 libFuzzer 集成进行模糊测试。fuzz test(模糊测试?)是一种发现意外错误的策略。
cargo install cargo-fuzz
cargo help fuzz
cargo fuzz new myfuzztest
cargo fuzz run myfuzztest
参考:
https://llvm.org/docs/LibFuzzer.html
9、cargo-watch
在代码更改时自动重新运行 Cargo 命令。
cargo install cargo-watch
cargo help watch
# 持续运行 cargo check
cargo watch
# 有变更时重建文档
cargo watch -x doc
10、cargo-tree
可视化项目依赖树。
cargo install cargo-tree
cargo help tree
cargo tree
Chapter 4 数据结构
1、String、str、&str 和 &'static str
- str,栈(stack)分配的 UTF-8 字符串,可以被借用但不能移动或改变
- String,堆(heap)分配的 UTF-8 字符串,可以被借用和修改
- &str,指向 str 或 String 及其长度的指针
- &'static str,指向 str 及其长度的指针,进程整个生命周期内都有效
2、Array 和 Slice
数组是一个固定长度的值序列,这些值在编译时已知。
切片是指向连续内存区域的指针,包括长度,代表任意长度的值序列。
切片和数组都可以被递归地解构为不重叠的子切片。
你可以多次借用同一个切片或数组,因为切片不会重叠。
let wordlist = "one,two,three,four";
for word in wordlist.split(‘,’) {
println!("word={}", word);
}
3、Vectors
String 基于 Vec,String 类型是一个 Vec<u8>
,见 https://doc.rust-lang.org/src/alloc/string.rs.html#365-367 。
Vec 是在 Rust 中分配堆内存的方法之一(另一种是智能指针,例如 Box)。
Vec 是一种特殊的类型,它同时是 Vec 和切片。
在你担心分配过大的连续内存区域或者关心内存的位置时,你可以通过简单地将 Box 填充到 Vec 中(即使用 Vec<Box<T>>
)来轻松解决这个问题。
VecDeque,双端队列
LinkedList,双向链表
HashMap
BTreeMap
HashSet
BTreeSet
BinaryHeap,使用二叉堆实现的优先队列,内部使用 Vec
参考:
https://doc.rust-lang.org/std/collections/index.html
4、HashMap
HashMap 使用 Siphash-1-3 函数进行哈希处理,也可以为 HashMap 提供自己的哈希函数。
自定义哈希函数。
use metrohash::MetroBuildHasher;
use std::collection::HashMap;
let mut map = HashMap::<String, String, MetroBuildHasher>::default();
创建 hashable 类型。
HashMap 可以用于任意键和值,但键必须实现 std::cmp::Eq 和 std::hash::Hash 特性。许多特性,如 Eq 和 Hash ,可以使用 #[derive]
属性自动派生。注意需要 derive PartialEq,因为 Eq 依赖 PartialEq。
#[derive(Hash, Eq, PartialEq, Debug)]
struct CompoundKey {
name: String,
value: i32,
}
参考:
https://www.jandrewrogers.com/2015/05/27/metrohash/
5、基本类型。
size 是平台依赖的尺寸,通常在 32 位和 64 位系统中分别为 32 位或 64 位长度。比如 usize 和 isize。
元组最常见的用途是从函数返回多个值。
fn swap<A, B>(a: A, b: B) -> (B, A) {
(b, a)
}
结构体有两个变种,1)空结构体,2)元组结构体。
struct Foo;
struct Foo(String);
let foo = Foo("foo".into());
println("{foo.0}");
枚举可被认为是一种特殊的结构体,包含了枚举的互斥变体。枚举在给定时间只能是其变体之一。由于枚举类型是有序的,所以可以转换为 u32,而 u32 转 Enum 需要用 From trait。
enum Foo {
Bar,
Hoo,
}
impl From<u32> for Foo {
fn from(other: u32) -> Self {
Foo.Hoo
}
}
println("{:?}", Foo.Hoo as u32);
定义一个别名并不会创建一个新类型。
type Foo = HashMap<String, Bar>;
6、用 From 和 Into Trait 转换类型。
通常来说,你只需要实现 From 特质,几乎永远不需要实现 Into 。 Into 特质是 From 的互逆,并且将由编译器自动派生。
impl From<std::io::Error> for Error {
fn from(other: std::io::Error) -> Self {
Self(other.to_string())
}
}
遇到可能失败的转换过程,可以用 TryFrom 或 TryInto Trait。
impl TryFrom<&str> for i32 {
type Error = std::num::ParseIntError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.parse::<i32>()
}
}
7、处理 FFI 与 Rust 类型的兼容性。
Rust 团队提供了一个名为 rust-bindgen 的工具。通过 rust-bindgen ,你可以从 C 头文件自动生成绑定到 C 库的绑定。大多数情况下,你应该使用 rust-bindgen 来生成绑定。
参考:
https://rust-lang.github.io/rust-bindgen/introduction.html
Chapter 05
1、堆(Heap)和栈(Stack)
堆是用于动态分配的内存区域。通过使用任何堆分配的数据结构来实现堆上分配,例如 Vec 或 Box。注:String 也是 Vec,所以也是堆。
let heap_integer = Box::new(1);
let heap_integer_vec = vec![0; 100];
let heap_string = String::from("heap string");
栈