Rust面试笔记

241 阅读9分钟

Rust基础

juejin.cn/column/7220…

course.rs/first-try/i…

huangjj27.github.io/interview.h…

生命周期

course.rs/advance/lif…

智能指针

juejin.cn/post/722376…

course.rs/advance/sma…

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!

参数数量支持可变,编译时展开

juejin.cn/post/722376…

course.rs/advance/mac…

Send 和 Sync

  • Send:类型可以安全地转移所有权到另一个线程

  • Sync:类型可以安全地被多个线程同时引用(&T)

juejin.cn/post/725806…

juejin.cn/post/725806…

course.rs/advance/con…

Pin 和 Unpin

juejin.cn/post/725819…

course.rs/advance/asy…

Rust 中的 FnFnMutFnOnce Trait

  • FnOnce:表示闭包可以被调用一次,调用后闭包本身会被消耗(consume),无法再次使用。
  • FnMut:表示闭包可以被多次调用,并且在调用时可以修改捕获的变量。
  • Fn:表示闭包可以被多次调用,并且在调用时只读取捕获的变量,不进行修改。 juejin.cn/post/747482…

Rust中的关联类型

维度泛型参数 Trait<T>关联类型 Trait { type T; }
一个类型的实现次数一个类型可以实现多次(如 Add<i32>Add<String>一个类型只能实现一次
调用时的显式程度必须显式写出类型参数 foo::<T>()编译器自动推断,无需显式标注
表达的关系trait 与外部类型的关系trait 与"自身输出/组成"的关系
代码可读性容易在签名中堆积类型参数更简洁,类型参数隐藏于内部

juejin.cn/post/722896…

tokio

为什么 Tokio 需要自己的sync::Mutex,而不能直接用标准库的std::sync::Mutex

标准库的std::sync::Mutex是为同步场景设计的,其lock方法会阻塞当前线程直到获取锁。在异步任务中使用时,若锁被长时间持有,会阻塞 Tokio 的工作线程,导致其他任务无法执行,破坏异步效率。

Tokio 的sync::Mutex异步感知的

  • lock方法返回Future,调用时会await,若锁被占用,任务会让出线程,允许其他任务运行;
  • 避免了线程阻塞,更适合异步环境中的共享状态管理。

join!

并发运行多个异步任务(Future),等待所有任务完成后,收集它们的结果。

select!

作用:同时等待多个异步任务,当其中第一个任务完成时,执行对应的分支逻辑(其他未完成的任务会被取消)。

用途:处理 “抢占式” 场景,如 “超时控制”“多源数据竞争” 等。

零拷贝

层级代码实体解决的核心问题代价
语言层&[u8]&str'a安全借用:谁能读、谁不能改、能活多久受生命周期约束,无法逃逸作用域
库层BytesCowAsRef灵活共享:跨函数、跨线程传递,无需关心原始所有者Bytes 有原子引用计数开销(极小)
系统层mmapsendfilewritev绕过 CPU:内核直接搬运,不经过用户态 buffer依赖 OS 支持,有页对齐/长度限制
应用层IoSlice、serde &'de strzerocopy直接操作:解析即视图,发送即切片,不还原数据数据必须是不需修改的(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]BytesString 都能直接传入,统一零拷贝入口,无需为每种类型写重载。

三、系统层:内核空间直接搬运,绕过 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_vectoredwritev 系统调用在 Rust 中拥有类型安全接口。把 headerbody 等多个 &[u8] 直接传给内核 gather 发送,用户态零拼接
serde 的 &'de strSerde 反序列化生命周期参数 'de 与输入 buffer 绑定。当结构体字段声明为 &'de str 并加 #[serde(borrow)] 时,解析出的字符串直接指向原始 JSON / MessagePack buffer 内部的切片反序列化时不为字符串字段分配 String,直接从原始字节流零拷贝取出视图。'de 保证该视图不会比输入 buffer 活得更长。
zerocopyCrate 提供的 unsafe trait(如 FromBytesAsBytesFromZeroes)。允许将 &[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 / 同步开销
  • 合理使用分片、分片锁