一、Rust 中级题
1. 为什么 usize 传参不会丢失所有权,而 String 会?
核心结论
usize 实现了 Copy,传参时发生拷贝;String 未实现 Copy,传参时发生 move。
关键概念
CopyvsMove- 栈类型 vs 堆分配类型
代码示例
fn takes_usize(x: usize) {
println!("{}", x);
}
fn takes_string(s: String) {
println!("{}", s);
}
fn main() {
let a = 10;
takes_usize(a);
println!("{}", a); // OK
let s = String::from("hello");
takes_string(s);
// println!("{}", s); // 编译错误:value moved
}
2. 什么是 Copy trait?
核心结论
实现 Copy 的类型在赋值或传参时按位复制,而非转移所有权。
关键概念
- 隐式拷贝
- 无析构逻辑(no Drop)
代码示例
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // copy
println!("{} {}", p1.x, p2.x);
}
3. &str 引用的数据存储在哪里?
核心结论
&str 只是一个切片,数据位置取决于其来源。
关键概念
- 字符串字面量
- 切片
代码示例
fn main() {
let a: &str = "hello"; // 二进制 data 段
let s = String::from("world");
let b: &str = &s; // 指向堆内存
}
4. 什么是生命周期(lifetime)?
核心结论
生命周期用于描述引用在多长时间内是合法的。
关键概念
- 借用检查器
- 防止悬垂引用
代码示例
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
5. 什么是 'static 引用?
核心结论
'static 引用在整个程序生命周期内都有效。
关键概念
- 程序级生命周期
代码示例
fn get_static() -> &'static str {
"hello world"
}
6. 为什么 &'static str 可以赋值给 &str?
核心结论
Rust 允许生命周期缩短(lifetime coercion)。
关键概念
'staticoutlives 所有生命周期
代码示例
fn main() {
let s: &'static str = "hello";
let t: &str = s; // OK
}
7. 如何把值从栈移动到堆?
核心结论
使用 Box<T> 或其他堆分配智能指针。
关键概念
- 堆分配
代码示例
fn main() {
let x = 5;
let boxed = Box::new(x);
}
8. 为什么 &Box<T> 可以当作 &T 使用?
核心结论
因为 Box<T> 实现了 Deref<Target = T>。
关键概念
- Deref coercion
代码示例
fn takes_ref(t: &i32) {
println!("{}", t);
}
fn main() {
let b = Box::new(10);
takes_ref(&b); // 自动解引用
}
9. 如何在不传 &mut 的情况下实现可变性?
核心结论
使用内部可变性(interior mutability)。
关键概念
RefCell
代码示例
use std::cell::RefCell;
fn main() {
let x = RefCell::new(5);
*x.borrow_mut() += 1;
println!("{}", x.borrow());
}
二、Rust 高级题
10. 单线程 / 多线程的共享所有权如何实现?
核心结论
- 单线程:
Rc - 多线程:
Arc
关键概念
- 引用计数
- 线程安全
代码示例
use std::rc::Rc;
use std::sync::Arc;
fn main() {
let a = Rc::new(5);
let b = a.clone();
let x = Arc::new(10);
let y = x.clone();
}
11. 为什么 Rc<T> / Arc<T> 默认不可变?
核心结论
共享所有权默认只读,避免数据竞争。
解决方式
结合内部可变性。
代码示例
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let v = Rc::new(RefCell::new(1));
*v.borrow_mut() += 1;
}
12. 生命周期中的子类型关系如何理解?
核心结论
生命周期越长,类型越“具体”,是较短生命周期的子类型。
关键概念
- 协变(variance)
代码示例
fn takes_short<'a>(x: &'a str) {}
fn main() {
let s: &'static str = "hello";
takes_short(s); // OK
}
13. 泛型约束 vs trait object 的取舍?
核心结论
- 泛型:静态分发,性能好
- trait object:动态分发,灵活
代码示例
trait Foo { fn foo(&self); }
fn generic<T: Foo>(t: T) { t.foo(); }
fn dynamic(t: &dyn Foo) { t.foo(); }
14. 'static 作为 trait bound 通常用在什么场景?
核心结论
保证类型在异步或跨线程场景下不会引用短生命周期数据。
关键概念
- async / Future
代码示例
use std::thread;
fn spawn<T: Send + 'static>(v: T) {
thread::spawn(move || {
drop(v);
});
}
三、Rust 架构题
这一部分面向 高级工程师 / 架构设计 / Rust 核心机制理解,重点考察对并发、异步、内存模型和 unsafe 边界的理解。
15. Send 和 Sync 的区别是什么?
核心结论
Send:类型的所有权可以在线程间移动Sync:类型的共享引用&T可以在线程间共享
关键概念
- 自动 trait(auto trait)
- 数据竞争的编译期预防
代码示例
use std::rc::Rc;
use std::sync::Arc;
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
fn main() {
// Rc 既不是 Send 也不是 Sync
// assert_send::<Rc<i32>>();
// Arc 同时是 Send + Sync
assert_send::<Arc<i32>>();
assert_sync::<Arc<i32>>();
}
16. 为什么 RefCell<T> 不是 Sync?
核心结论
RefCell 通过运行期借用检查实现可变性,无法保证线程安全。
关键概念
- 运行期借用检查
- 非原子性状态
代码示例
use std::cell::RefCell;
fn assert_sync<T: Sync>() {}
fn main() {
// 编译失败:RefCell<T> 不是 Sync
// assert_sync::<RefCell<i32>>();
}
17. Mutex<T> 和 RwLock<T> 的核心区别是什么?
核心结论
Mutex:同时只允许一个线程访问RwLock:允许多个读者或一个写者
关键概念
- 读多写少场景
- 锁竞争
代码示例
use std::sync::{Mutex, RwLock};
fn main() {
letm = Mutex::new(5);
*m.lock().unwrap() += 1;
letr = RwLock::new(10);
*r.write().unwrap() += 1;
}
18. 什么是 Pin?它解决了什么问题?
核心结论
Pin 用于保证某个值在内存中的地址不会再发生移动。
关键概念
- 自引用结构体
- async / Future 状态机
代码示例
use std::pin::Pin;
struct SelfRef {
data: String,
ptr: *constString,
}
// Pin 防止 SelfRef 被移动,避免 ptr 悬空
19. Unpin 是什么?
核心结论
Unpin 表示类型在被 Pin 之后仍然可以安全移动。
关键概念
- 大多数普通类型都是 Unpin
- async 生成的 Future 通常不是 Unpin
代码示例
fn assert_unpin<T: Unpin>() {}
fn main() {
assert_unpin::<i32>();
}
20. async/await 在 Rust 中是如何实现的?
核心结论
async fn 会被编译器转换为一个实现了 Future 的状态机。
关键概念
- 零成本抽象
- 状态机展开
代码示例
use std::future::Future;
async fn foo() -> i32 {
42
}
fn assert_future<F: Future<Output = i32>>(_: F) {}
fnmain() {
assert_future(foo());
}
21. 为什么 Future 经常需要 'static?
核心结论
为了确保 Future 在跨线程或长期运行时不会引用已经失效的数据。
关键概念
- 任务调度器
- 生命周期逃逸
代码示例
use std::thread;
fn spawn<F>(f: F)
where
F: FnOnce() + Send + 'static,
{
thread::spawn(f);
}
22. 什么是 unsafe?Rust 的安全边界在哪里?
核心结论
unsafe 不是关闭安全,而是由程序员手动保证 Rust 的安全不变量。
关键概念
- 安全抽象包裹 unsafe
- 最小 unsafe 原则
代码示例
unsafe fn dangerous(ptr: *consti32) -> i32 {
*ptr
}
23. 什么情况下你会在架构层面选择 unsafe?
核心结论
- 性能关键路径
- 与底层系统 / FFI 交互
- 实现安全抽象(如 Vec、Arc)
关键概念
- 不变量设计
- 封装与审计