Rust基础
huangjj27.github.io/interview.h…
生命周期
智能指针
Box<T> 堆上分配值并在离开作用域时自动释放内存
Rc<T> 单线程,多个指针指向同一个值,当最后一个指针离开作用域时,值将被释放
Arc<T> 多线程,多个指针指向同一个值,当最后一个指针离开作用域时,值将被释放
Cell<T> 允许你在不可变引用的情况下修改内部值,只能用于`Copy`类型
`get()`:获取 `Cell<T>` 中存储的值的不可变引用。
`set(value: T)`:将新的值存储到 `Cell<T>` 中,替换旧值。
`replace(&self, value: T) -> T`:替换 `Cell<T>` 中存储的值,并返回旧值。
`into_inner(self) -> T`:从 `Cell<T>` 中提取存储的值,并返回它本身。
RefCell<T> 允许你在不可变引用的情况下修改内部值,可以用于非`Copy`类型
`Ref<T>` 是 `RefCell<T>` 内部值的不可变借用。通过 `borrow()` 方法获取,它提供了对 `RefCell<T>` 内部值的不可变访问。
`RefMut<T>` 是 `RefCell<T>` 内部值的可变借用。通过 `borrow_mut()` 方法获取,它提供了对 `RefCell<T>` 内部值的可变访问。
UnSafeCell<T> 允许你在不可变引用的情况下修改内部值,不提供任何运行时检查来确保安全性
Weak<T> 弱引用,不会增加引用计数,因此它不会阻止值被释放。当你希望创建一个循环引用时,可以使用`Rc<T>`或`Arc<T>`和`Weak<T>`来实现。
as
| 方法 | 输入 | 输出 | 核心能力 | 典型目标 |
|---|---|---|---|---|
as_ref() | Option<T> | Option<&T> | 阻止 T 被 move,只读借用 | 临时查看、匹配、传参 |
as_mut() | Option<T> | Option<&mut T> | 阻止 T 被 move,可变借用 | 原地修改内部值 |
as_deref() | Option<T> | Option<&U> | 穿透一层智能指针,只读解引用 | Option<String> → Option<&str> |
as_deref_mut() | Option<T> | Option<&mut U> | 穿透一层智能指针,可变解引用 | Option<Vec<T>> → Option<&mut [T]> |
宏
| 类型 | 代表 | 工作层级 | 典型用途 |
|---|---|---|---|
| 声明宏 | macro_rules! | 基于词法树(Token Tree)做模式匹配和替换 | vec![]、println!、assert_eq! |
| 过程宏 | derive / attribute / function-like | 直接操作编译器 AST,用 Rust 代码生成 Rust 代码 | #[derive(Serialize)]、sqlx::query! |
参数数量支持可变,编译时展开
Send 和 Sync
-
Send:类型可以安全地转移所有权到另一个线程
-
Sync:类型可以安全地被多个线程同时引用(&T)
Pin 和 Unpin
Rust 中的 Fn、FnMut 和 FnOnce Trait
FnOnce:表示闭包可以被调用一次,调用后闭包本身会被消耗(consume),无法再次使用。FnMut:表示闭包可以被多次调用,并且在调用时可以修改捕获的变量。Fn:表示闭包可以被多次调用,并且在调用时只读取捕获的变量,不进行修改。 juejin.cn/post/747482…
Rust中的关联类型
| 维度 | 泛型参数 Trait<T> | 关联类型 Trait { type T; } |
|---|---|---|
| 一个类型的实现次数 | 一个类型可以实现多次(如 Add<i32>、Add<String>) | 一个类型只能实现一次 |
| 调用时的显式程度 | 必须显式写出类型参数 foo::<T>() | 编译器自动推断,无需显式标注 |
| 表达的关系 | trait 与外部类型的关系 | trait 与"自身输出/组成"的关系 |
| 代码可读性 | 容易在签名中堆积类型参数 | 更简洁,类型参数隐藏于内部 |
tokio
为什么 Tokio 需要自己的sync::Mutex,而不能直接用标准库的std::sync::Mutex?
标准库的std::sync::Mutex是为同步场景设计的,其lock方法会阻塞当前线程直到获取锁。在异步任务中使用时,若锁被长时间持有,会阻塞 Tokio 的工作线程,导致其他任务无法执行,破坏异步效率。
Tokio 的sync::Mutex是异步感知的:
- 其
lock方法返回Future,调用时会await,若锁被占用,任务会让出线程,允许其他任务运行; - 避免了线程阻塞,更适合异步环境中的共享状态管理。
join!
并发运行多个异步任务(Future),等待所有任务完成后,收集它们的结果。
select!
作用:同时等待多个异步任务,当其中第一个任务完成时,执行对应的分支逻辑(其他未完成的任务会被取消)。
用途:处理 “抢占式” 场景,如 “超时控制”“多源数据竞争” 等。
零拷贝
| 层级 | 代码实体 | 解决的核心问题 | 代价 |
|---|---|---|---|
| 语言层 | &[u8]、&str、'a | 安全借用:谁能读、谁不能改、能活多久 | 受生命周期约束,无法逃逸作用域 |
| 库层 | Bytes、Cow、AsRef | 灵活共享:跨函数、跨线程传递,无需关心原始所有者 | Bytes 有原子引用计数开销(极小) |
| 系统层 | mmap、sendfile、writev | 绕过 CPU:内核直接搬运,不经过用户态 buffer | 依赖 OS 支持,有页对齐/长度限制 |
| 应用层 | IoSlice、serde &'de str、zerocopy | 直接操作:解析即视图,发送即切片,不还原数据 | 数据必须是不需修改的(immutable) |
一、语言层:编译器保证的零拷贝安全
这一层的机制全部内建在 Rust 类型系统中,零运行时开销。
| 关键字 | 作用 | 零拷贝价值 |
|---|---|---|
&[u8] | 对任意连续内存的只读字节视图。不拥有底层数据,只是一个指向已有内存(如 Vec<u8>、数组、栈内存)的胖指针(ptr + len)。 | 让函数接受"一段字节"时,无需把 Vec 或数组复制进新的容器,直接传递引用即可读取。 |
&str | 对合法 UTF-8 内存的只读字符串视图。在 &[u8] 基础上附加了编码合法性保证,编译期确保分割不会破坏 UTF-8 边界。 | 零拷贝传递文本数据时,同时获得内存安全和编码安全,避免为了校验而复制内容到新的 String。 |
'a | 生命周期泛型参数。标记引用从哪创建、到哪失效,参与类型检查与借用检查器(Borrow Checker)的推理。 | 在编译期证明零拷贝引用永远不会比它指向的数据活得更长,彻底杜绝悬垂指针,让"只看不拿"成为可组合的安全契约。 |
二、库层:跨模块、跨线程的零拷贝共享
语言层的 &T 受限于单一作用域和所有者模型。库层通过引用计数和智能枚举,把零拷贝扩展到复杂的所有权与线程场景。
| 关键字 | 作用 | 零拷贝价值 |
|---|---|---|
Bytes | 一个引用计数的不可变字节容器。底层可能是 Vec<u8>、&'static [u8] 或 mmap 内存,通过原子引用计数(Arc 语义)管理生命周期。 | clone() 是 O(1) 的原子操作而非深拷贝,让 buffer 可以在多线程、多异步任务间自由传递而无需复制 1 字节。 |
Cow<'a, B> | Clone on Write 枚举。两个变体:Borrowed(&'a B) 表示零拷贝借用;Owned(<B as ToOwned>::Owned) 表示拥有型数据。 | 解决**"大概率不改,偶尔需要改"**的场景:先以 Borrowed 零拷贝借用,仅在真正需要修改时才延迟执行深拷贝(to_mut() 触发),避免提前分配。 |
AsRef<T> | 零成本视图转换 Trait。定义 as_ref(&self) -> &T,表示"我可以被零拷贝地视为 T 的引用"。 | 函数参数声明为 impl AsRef<[u8]> 时,Vec<u8>、&[u8]、Bytes、String 都能直接传入,统一零拷贝入口,无需为每种类型写重载。 |
三、系统层:内核空间直接搬运,绕过 CPU
这一层与 Rust 语言特性无关,但 Rust 通过封装将其安全接入。核心目标是消除"内核 ↔ 用户"之间的冗余 memcpy。
| 关键字 | 作用 | 零拷贝价值 |
|---|---|---|
mmap | 内存映射。将内核的文件页缓存(Page Cache)直接映射到进程的用户虚拟地址空间。用户得到的 &[u8] 与内核看到的是同一块物理内存(或按需加载的页)。 | 读取大文件时无需 read() 系统调用,也无需分配 Vec<u8> 把数据搬进用户空间,实现真正的 0 次拷贝文件访问。 |
sendfile | 内核内部转发系统调用。用户态只传入两个 fd(来源文件、目标 socket),内核直接把文件页缓存的内容发送到 socket 发送缓冲区。 | 数据全程不进入用户空间,从"磁盘 → 内核 Page Cache → 内核 Socket Buffer → 网卡"由内核或 DMA 直接搬运,用户态 CPU 不碰数据。 |
writev | 分散-聚集 I/O(Vectored I/O)。允许一次系统调用传入多个用户空间的不连续内存块(iovec 数组),内核按顺序 gather 读取并写入目标 fd。 | 避免在用户态把 HTTP Header、Body 等多段内存拼接成一个新的 Vec(节省一次用户态 memcpy),通过一次系统调用完成多段发送。 |
四、应用层:协议解析与 I/O 聚合中的直接操作
这一层把前三层的能力组合成业务可直接使用的零拷贝方案:解析时不还原字段,发送时不拼接 buffer。
| 关键字 | 作用 | 零拷贝价值 |
|---|---|---|
IoSlice | 标准库对 iovec 的安全封装。内部是一个 &[u8],表示用户空间的一块缓冲区引用,用于 write_vectored / read_vectored。 | 让 writev 系统调用在 Rust 中拥有类型安全接口。把 header、body 等多个 &[u8] 直接传给内核 gather 发送,用户态零拼接。 |
serde 的 &'de str | Serde 反序列化生命周期参数 'de 与输入 buffer 绑定。当结构体字段声明为 &'de str 并加 #[serde(borrow)] 时,解析出的字符串直接指向原始 JSON / MessagePack buffer 内部的切片。 | 反序列化时不为字符串字段分配 String,直接从原始字节流零拷贝取出视图。'de 保证该视图不会比输入 buffer 活得更长。 |
zerocopy | Crate 提供的 unsafe trait(如 FromBytes、AsBytes、FromZeroes)。允许将 &[u8] 按内存布局直接 reinterpret 为结构体引用(如 &UdpHeader)。 | 解析结构化二进制数据(网络协议头、文件头)时,无需逐字段 read_u16 / read_u32 并复制到结构体字段,直接按内存布局零拷贝转换,O(1) 解析。 |
高并发库
- dashmap:高并发无锁哈希表
- crossbeam-epoch:无锁数据结构内存回收
- flume:同步 + 异步通用通道
- rayon:数据并行、CPU 密集计算
- parking-lot:高性能锁,替代标准库锁
- seize:新一代无锁内存回收(业务开发用不到)
- crossbeam-channel:同步高性能通道(业务开发用不到)
Rust 高并发性能优化常用手段
- 用无锁 / 原子操作代替锁
- 用 dashmap 代替粗粒度锁哈希表
- 用 crossbeam-channel / flume 做消息传递
- 减少锁持有时间
- 避免锁嵌套
- 异步任务避免阻塞
- 批量处理减少 IO / 同步开销
- 合理使用分片、分片锁