Rust 笔记 ongoing
在文档中显示警告
在注释中通过 <div class="warning"></div> 显示警告.
/// Use this function with caution.
///
/// <div class="warning">
/// Describe what the warning is.
/// </div>
pub fn use_with_caution() {}
选择恰当的不可变引用
INFO
参考 YouTube 视频:
Choose the Right Option - by Logan Smith.
Five Strong Opinions on Everyday Rust - by Logan Smith.
| 正确 ✔️ | 错误 ❌ |
|---|---|
Option<&T> | &Option<T> |
&[T] | &Vec<T> |
&str | &String |
&T | &Box<T> |
不要对枚举类型使用通配符
假设声明了这样的枚举类型:
enum MyLongEnumName {
VarA,
VarB,
VarC,
}// 为了写起来简便可以使用别名.
use MyLongEnumName as E;
match entity {
E::VarA => todo!(),
E::VarB => todo!(),
E::VarC => todo!(),
}// 这个写法是错误的, 不要这么写.
// 因为如果枚举类型的定义中减少一个元素, 编译器则无法提供正确的信息.
use MyLongEnumName as *;
match entity {
VarA => todo!(),
VarB => todo!(),
VarC => todo!(),
}不要将 Into<T> 作为返回值
fn greet() -> String {
"hello".into()
}fn greet() -> impl Into<String> {
"hello"
}在设计 API 时, 为了灵活, 建议在函数的参数声明中使用 Into<T>.
pub fn nice_api(s: impl Into<String>) {
// 另外为了避免泛型带来的可执行文件体积膨胀问题,
// 考虑使用内部的非泛型函数来处理主要逻辑.
fn non_generic(s: String) {
s.do_something_wit().the_string();
}
non_generic(s.into())
}在 Unsafe 函数中不要省略 Unsafe Block
// 此编译选项可以确保 Unsafe 函数中没有省略 Unsafe Block
#![deny(unsafe_op_in_unsafe_fn)]判断是否静态链接 C 语言运行时
通过名为 crt-static 的 target_feature.
#[cfg(target_feature = "crt-static")]let 赋值时在右侧使用 else
要记得 else 可以灵活使用, 并不是只能配合 if.
let Ok(db_password) = std::env::var("DB_PASSWORD") else {
return Err(anyhow::anyhow!("DB_PASSWORD not set"));
};等同于以下写法:
let db_password = match std::env::var("DB_PASSWORD") {
Ok(password) => password,
Err(_) => return Err(anyhow::anyhow!("DB_PASSWORD not set")),
};在开发过程中屏蔽 unused 警告
在编写代码的过程中, rust-analyzer 最长出现的警告就是 dead_code 或 unused_xxx.
如果不希望这些警告在编写代码的途中对我们造成干扰, 可以添加 crate level linter config:
// 记得在恰当的时机将这一行注释掉, 恢复警告
#![allow(unused)]如果担心自己忘记移除, 可以这么写:
// 即使忘记将这行注释掉, 在不包含 debug 信息的编译时依然能触发警告
#![cfg_attr(debug_assertions, allow(unused))]WARNING
默认情况下 cargo build --release 会使用 Cargo.toml 中 [profile.release] 的设置.
请确保 [profile.release] 中未设置 debug-assertions = true, 否则上述 #![cfg_attr(xxx)] 会失效.
为方便自动测试而启用条件编译
如果我们希望在测试中直接比较两个自定义类型的变量, 则需要实现 PartialEq trait.
#[test]
fn equality_test() {
let a = MyStruct("data".to_owned());
let b = MyStruct("data".to_owned());
assert_eq!(format!("{a:?}"), format!("{b:?}"));
assert_eq!(a, b);
}如果只打算在测试中为自定义类型实现 PartialEq, 则可以借助 cfg_attr 特性.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
struct MyStruct(String);使用 CSS 修改文档显示效果
在代码的注释中使用 <div style=""></div> 可以渲染任意文档页面效果. 例如:
/// <div style="background-color:rgb(100%, 65%, 0%); width: 10px; padding: 10px; border: 1px solid;"></div>
pub const ORANGE: Color = Color::rgb(1.0, 0.65, 0.0);在文档中的渲染效果如下:

将函数标记为 deprecated
使用 #[deprecated = "xxx"] 来标记函数.
#[deprecated = "use `.read()` instead."]
pub fn iter(&mut self) -> RemovedIter<'_> {
self.read()
}判断浮点数是否相等性
借助 f32::EPSILON 来判断.
let x = 2.0_f32;
let abs_difference = (x.powi(2) - (x * x)).abs();
assert!(abs_difference <= f32::EPSILON); 借助 Vim 插件编辑 Closure
如果使用 vim (或 vim 快捷键) 来编写 Rust 代码, 在涉及到 Closure 时写起来可能不太顺手.
因为 Closure 中的参数部分被 | 字符包裹, 而 vim 默认不将 | 字符当作 text object.
也就是说例如快捷键
vi|不能选中被两个|字符包裹的文本.
此时可以借助 targets.vim 插件来拓展 text object 的种类.
用半开放的范围来模式匹配
从 Rust 1.75.0 起, 可以对 usize 和 isize 使用半开放的范围来做模式匹配:
fn match_range(number: usize) {
match number {
..=5 => todo!(),
6.. => todo!(),
}
}// 在某些平台中 usize 可能小于 usize::MIN 或者大于 usize::MAX.
// 同样, isize 可能小于 isize::MIN 或者大于 isize::MAX.
// 因此不应该使用这种方式来做模式匹配.
fn match_range(number: usize) {
match number {
usize::MIN..=5 => todo!(),
6..=usize::MAX => todo!(),
}
}函数式处理 Result 和 Option
从 Rust 1.76.0 起, Result 和 Option 类型新增了 inspect() 系列函数.
可以通过函数式的写法 (以往通常使用模式匹配), 处理它们内部的值.
let my_result: Result<&str, usize> = Ok("yahaha");
// 通过模式匹配
if let Ok(inner) = my_result {
// do something...
}
// 函数式的写法
my_result.inspect(|&inner| {
// do something...
}); let my_result: Result<&str, usize> = Ok("yahaha");
// 通过模式匹配
if let Err(inner) = my_result {
// do something...
}
// 函数式的写法
my_result.inspect_err(|&inner| {
// do something...
}); 当需要将多个函数串联起来使用时, 函数式的写法会更有优势, 例如:
let x: u8 = "4"
.parse::<u8>()
.inspect(|x| println!("original: {x}"))
.map(|x| x.pow(3))
.expect("failed to parse number");让操作系统分配网络端口
有些时候我们希望找到任意一个可用的网络端口 (例如为了发送 UDP 数据).
此时可以在调用 bind() 函数时将端口设置为 0, 操作系统就会为我们随机分配.
// bind 时将端口设置为 0, 操作系统就会随机分配可用端口
let socket = std::net::UdpSocket::bind("0.0.0.0:0")?;
// 可以通过 local_addr() 来获取被分配的端口
println!("udp socket bound to: {}", socket.local_addr()?);let socket = std::net::TcpListener::bind("0.0.0.0:0")?;
println!("tcp listener bound to: {}", socket.local_addr()?);恰当标记可能会 panic 的函数
在某些情况下, 我们可能会允许函数 panic:
可能是因为出于性能考虑, 在函数中省去了必要的检查;
也可能是因为存在 "不可恢复的错误", 因此理应 "fail fast".
在这种情况下, 应该恰当地标记这些可能会 panic 的函数,
使得调用者可以在编写代码时得知 "什么情况下会 panic";
以及在运行时遭遇 panic 时, 能快速且精准地 定位.
具体的写法:
我们应通过注释中的 # Panics 部分来说明 "什么情况下会 panic".
并使用 #[track_caller] 标记可能会 panic 的函数, 以便后续定位错误.
/// Parses a string into a u32.
///
/// # Panics
///
/// If the string cannot be parsed into a `u32` then the function will panic.
#[track_caller]
pub fn parse(text: &str) -> u32 {
text.parse::<u32>().unwrap()
}
fn main() {
parse("abc");
}2
3
4
5
6
7
8
9
10
11
12
13
运行程序的报错信息如下:
(请注意 panic 的位置为第 12 行而非第 8 行)
thread 'main' panicked at my_caller\src\main.rs:12:5:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }INFO
#[track_caller] 的作用是在编译时记录调用该函数的代码位置.
当函数内部 panic 时, 会打印该函数 被调用的位置 (而不是函数内部 panic 的位置).
这对于调试和定位错误非常有用,特别是在大型代码库中.
用 #[expect] 替代 #[allow]
Rust 1.81 推出了新特性, 也就是 #[expect(lint)].
它的作用类似于 #[allow(lint)], 都能使得一些本应出现的警告或错误被忽略.
但是 #[expect(lint)] 能够在 预期中的警告或错误未出现 时, 发出警告.
这能够提醒我们, 可能已经不再需要这条 "用于忽略某些错误或警告的语句" 了.
因此未来可以默认使用 #[expect] 替代 #[allow] (只要无需考虑 Rust 版本兼容性).
TIP
当打算将旧的项目中的所有 #[allow] 替换掉, 可以使用以下的全局配置:
使得 clippy (以及代码编辑器) 提醒我们应当将 #[allow] 替换为 #[expect].
#![deny(clippy::allow_attributes)]根据相关性将 impl 拆分
当我们为某个类型编写 impl 代码块时, impl 有时候会比较臃肿,
此时可以将 impl 代码块中的函数, 根据它们的相关性, 拆成多组.

为 lib 设计恰当的 API
阅读官方出品的 Rust API Guidelines.
并参考 9 Rust Best Practices with Real Lib (part 1/3).
