楼层: 首页/ 软件技术/ Rust 项目实战/ 面试重点:Rust 高频 30 问
06

面试重点:Rust 高频 30 问

Top 30 Rust Interview Questions

下面这 30 道是 Rust 面试的高频题,覆盖所有权、借用、生命周期、trait、智能指针、并发、异步、错误处理和性能。点开折叠看答案,建议先自己答一遍。

第一组 · 所有权与借用(1-8)

1. 什么是所有权?三大规则是什么?

答案

每个值有且只有一个 owner;owner 离开作用域值被 drop;同一时刻要么一个可变借用要么多个不可变借用。这是 Rust 内存安全的根基,没有 GC。

2. move 和 copy 的区别?

答案

Copy(如 i32、bool、&T)赋值时复制一份,原变量还能用;Move(如 String、Vec)赋值时转移所有权,原变量失效,防止两个变量同时释放同一块内存。

3. &T 和 &mut T 为什么不能同时存在?

答案

防止数据竞争:一个读一个写时,你读到的可能是写了一半的脏数据。Rust 编译期就禁止,这是"无畏并发"的基础。

4. 悬垂引用是什么?Rust 怎么防止?

答案

引用指向已释放的内存。Rust 通过生命周期检查:引用的存活时间不能超过被引用数据,编译期就拒绝。

5. String、&str、&[u8] 分别用在哪?

答案

String 是拥有所有权的堆字符串;&str 是切片(借用视图);&[u8] 是字节切片。函数参数优先用 &str,避免不必要的 clone。

6. clone() 贵不贵?什么时候必须用?

答案

贵——深拷贝。能用引用就别 clone;需要真正独立的数据副本、或跨线程要求 'static 时才 clone。共享用 Arc。

7. 什么是 NRVO / 返回引用还是值?

答案

现代 Rust 返回小数据类型值即可,编译器会优化。返回引用必须保证引用比函数活得久,否则返回 owned 值。

8. Box::leak 是干嘛的,危险在哪?

答案

把 Box 故意泄漏成 'static,内存永不释放。只在真正常驻时用,滥用就是内存泄漏。

第二组 · 生命周期与 trait(9-16)

9. 生命周期标注 'a 到底在告诉编译器什么?

答案

告诉编译器:返回值的引用存活时间 = 两个输入引用中较短的那个。它不改变运行时行为,只帮编译器做检查。

10. 'static 是指"永远存活"吗?

答案

字面量字符串天然是 'static(编译期就放进二进制);但 'static 也可以是运行时分配且永不释放的数据。不是所有 'static 都在静态区。

11. trait 和 trait 对象的区别?

答案

泛型 trait(<T: Foo>)是单态化,编译期生成具体代码,零开销;Box<dyn Foo> 是 trait 对象,动态分发,有间接调用开销但类型可变。

12. Sized 和 ?Sized 是什么?

答案

Sized = 编译期知道大小。str 和 dyn Trait 是 unsized(胖指针),必须用 & 或 Box 包着。泛型默认要求 Sized。

13. From 和 Into 的关系?TryFrom 呢?

答案

实现 From<A> for B 就自动有 Into<B> for A。TryFrom 用于可能失败的转换(如把 u64 转 u8),返回 Result。

14. Deref 强制转换解决什么?

答案

让 &String 能自动当 &str 用、&Box<T> 当 &T 用,少写一堆解引用,智能指针用着像原生类型。

15. impl Trait 在参数和返回值位置各代表什么?

答案

参数位置是"接受任何实现了 Trait 的类型";返回值位置是"返回某个具体实现了 Trait 的类型,但不对外暴露类型名",等价于一个匿名泛型。

16. trait 默认方法和 trait 继承(Trait: Foo)?

答案

默认方法提供通用实现,实现者可覆盖。Trait: Foo 表示"实现本 trait 的类型必须也实现 Foo",叫 supertrait。

第三组 · 智能指针与并发(17-24)

17. Box、Rc、Arc、RefCell、Mutex 分别什么时候用?

答案

Box 唯一所有权堆分配;Rc 单线程多所有者;Arc 多线程多所有者;RefCell 单线程运行时借用检查;Mutex 多线程互斥访问。

18. Rc<RefCell<T>> 和 Arc<Mutex<T>> 的组合思想?

答案

外层管"多个所有者",内层管"可变访问"。单线程用 Rc+RefCell,多线程换成 Arc+Mutex,这是内部可变性模式的标准搭配。

19. Send 和 Sync 标记 trait 是什么意思?

答案

Send = 类型可以安全地转移到另一个线程;Sync = &T 可以安全地跨线程共享(即 T 能被多线程同时读)。不安全类型(如裸指针)默认两者都不是。

20. 什么情况下会发生死锁?Rust 能防吗?

答案

两个线程各持一把锁又互相等对方的锁。Rust 编译期防数据竞争,但防不了死锁——死锁是逻辑问题。规范:永远按固定顺序拿锁、缩短持锁时间。

21. Mutex<T> 为什么是智能指针?lock() 返回的 Guard 怎么用?

答案

lock() 返回 MutexGuard,它实现了 Deref/DerefMut,能像 &mut T 一样操作数据;Guard 离开作用域自动解锁。

22. channel 的 mpsc 是哪三个单词?有界无界怎么选?

答案

multiple producer, single consumer。无界 channel 可能撑爆内存(生产者太快),高吞吐生产环境用有界 channel 做背压。

23. tokio::spawn 和 std::thread::spawn 区别?

答案

std::thread 是操作系统真线程,重;tokio::spawn 是轻量级协程(任务),跑在异步运行时上,几十万个也没事,但不能阻塞。

24. async fn 为什么不能是 trait 方法?(历史原因)

答案

老版本 dyn trait 不支持 async(RPITit),得用 #[async_trait] 宏把返回值 Box 化。新 Rust 已部分支持,但生态里仍大量见 #[async_trait]。

第四组 · 错误处理、异步与性能(25-30)

25. thiserror 和 anyhow 区别?各用在哪?

答案

thiserror 写库的错误类型(定义自己的枚举,派生 Error);anyhow 写应用层(把各种错误打包成一个盒子,往上抛就行)。库用 thiserror,binary 用 anyhow。

26. ? 操作符到底干了什么?

答案

是 match Result 的语法糖:是 Ok(v) 就取出 v 继续;是 Err(e) 就用 From 把错误转成函数返回类型的错误,然后提前 return。

27. unwrap() 和 expect() 的生产环境问题?

答案

遇到 None/Err 直接 panic。测试代码随便用,生产代码要换成明确的错误处理或带上下文的 ok()?。clippy 会警告。

28. 为什么 async 函数里不能直接阻塞(std::thread::sleep)?

答案

阻塞会卡住整个 worker 线程,同线程上的其他异步任务全被拖累。要用 tokio::time::sleep(让出执行权)或 spawn_blocking 跑阻塞活。

29. lto = "fat" 和 "thin" 怎么选?

答案

fat 优化更彻底但编译极慢;thin 大部分收益、编译快很多。大型项目(几千 crate)用 thin,小项目用 fat。

30. 项目里你遇到最难的 Rust 问题是什么?怎么解决的?

答案

这是开放题。准备一个真实故事:借用检查报错 → 读完整错误信息 → 定位生命周期问题 → 用 Arc/Mutex 重构 → 跑通。重点讲你怎么读编译器报错、怎么用工具定位,而不是背答案。

记
本章小结

① 所有权/借用/生命周期是必考题,核心是"编译期保证内存安全,无 GC"。

② Send/Sync、Arc/Mutex、channel 是并发题的三件套。

③ thiserror/anyhow、? 操作符、Release profile 是工程题高频点。