本页目录
Send 与 Sync
Send 和 Sync 是 Rust 并发安全的基石——Send 允许类型跨线程转移所有权,Sync 允许类型跨线程共享引用。它们是 auto trait(编译器自动推导):一个类型的所有字段都是 Send,这个类型就自动是 Send。PhantomData 用来手动静音或恢复自动推导——在不安全代码和 FFI 里至关重要。
Send 与 Sync 是什么
Send 和 Sync 是 Rust 并发安全的核心——它们是编译期的 marker traits,标记一个类型是否可以跨线程使用。
Send: 这个类型的所有权可以在线程间转移。如果一个类型的所有字段都是 Send,它就是 Send——编译器自动推导。Rc<T> 不是 Send——它的引用计数操作不是原子的,移入另一个线程可能导致计数错误。
Sync: 这个类型可以通过共享引用(&T)在线程间访问。形式化定义:T: Sync 当且仅当 &T: Send。RefCell<T> 不是 Sync——它的运行时 borrow check 不保证线程安全。Mutex<T> 是 Sync(当 T: Send)——Mutex 用锁保护内部,允许多线程通过 &Mutex<T> 安全访问。
编译器如何推导
编译器检查类型的每个字段:如果所有字段都是 Send,类型自动是 Send。如果所有字段都是 Sync,类型自动是 Sync。只有引入裸指针(*const T, *mut T)或非线程安全原语(Rc, RefCell, Cell, UnsafeCell)时,类型才会失去这些属性。
; // OK
; // OK (Arc 用原子操作同步)
// assert_send::<Rc<String>>(); // COMPILE ERROR
// assert_send::<*const i32>(); // COMPILE ERROR — 裸指针没有所有权语义
; // OK (Mutex 保证内部同步)
// assert_sync::<RefCell<i32>>(); // COMPILE ERROR
PhantomData 用于手动控制
用裸指针实现自定义容器时,裸指针不携带 Send/Sync——但你的容器逻辑上可能有这些保证:
use PhantomData;
use Rc;
// 虽然 MyNonSend 内部只有 i32(是 Send), 但 PhantomData<Rc<()>> 让它变成 !Send
PhantomData 不占内存(ZST),只在类型层面做标记。unsafe 代码的作者需要用 PhantomData 向编译器传达"这个类型应该/不应该 Send/Sync"——然后编译器会为调用方做正确性检查。
为什么不是"所有类型默认 Send"
标准库的类型经过精心设计:Rc<T> 天然不 Send,Mutex<T> 天然 Sync。第三方库中任何引入内部可变性的类型都需要仔细考虑 Send/Sync 的传播。如果一个类型意外地变成了 Send 而实际上不是线程安全的——那会是数据竞争的后门。这种"自动推导 + 人能介入"的设计让 Rust 在性能(零开销)和安全(编译期保证)之间取得平衡。
参考
- Rustonomicon: Send and Sync
- Rust Reference: auto traits
Keywords: Send, Sync, auto trait, PhantomData, thread safety, marker type, Rc, RefCell