425 - 《读书笔记:Code Like a Pro in Rust》

发布于 2024年3月24日

周末翻完的,适合有一定 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");

内容预览已结束

此内容需要会员权限。请先登录以查看完整内容。