本页目录

Send 与 Sync

Send 和 Sync 是 Rust 并发安全的基石——Send 允许类型跨线程转移所有权,Sync 允许类型跨线程共享引用。它们是 auto trait(编译器自动推导):一个类型的所有字段都是 Send,这个类型就自动是 Send。PhantomData 用来手动静音或恢复自动推导——在不安全代码和 FFI 里至关重要。

Send 与 Sync 是什么

SendSync 是 Rust 并发安全的核心——它们是编译期的 marker traits,标记一个类型是否可以跨线程使用。

Send: 这个类型的所有权可以在线程间转移。如果一个类型的所有字段都是 Send,它就是 Send——编译器自动推导。Rc<T> 不是 Send——它的引用计数操作不是原子的,移入另一个线程可能导致计数错误。

Sync: 这个类型可以通过共享引用⁠(&T)在线程间访问。形式化定义:T: Sync 当且仅当 &T: SendRefCell<T> 不是 Sync——它的运行时 borrow check 不保证线程安全。Mutex<T>Sync(当 T: Send)——Mutex 用锁保护内部,允许多线程通过 &Mutex<T> 安全访问。

编译器如何推导

编译器检查类型的每个字段:如果所有字段都是 Send,类型自动是 Send。如果所有字段都是 Sync,类型自动是 Sync。只有引入裸指针⁠(*const T, *mut T)或非线程安全原语⁠(Rc, RefCell, Cell, UnsafeCell)时,类型才会失去这些属性。

fn assert_send<T: Send>() {}
assert_send::<i32>();                // OK
assert_send::<Arc<String>>();        // OK (Arc 用原子操作同步)
// assert_send::<Rc<String>>();      // COMPILE ERROR
// assert_send::<*const i32>();      // COMPILE ERROR — 裸指针没有所有权语义

fn assert_sync<T: Sync>() {}
assert_sync::<Mutex<i32>>();         // OK (Mutex 保证内部同步)
// assert_sync::<RefCell<i32>>();    // COMPILE ERROR

PhantomData 用于手动控制

用裸指针实现自定义容器时,裸指针不携带 Send/Sync——但你的容器逻辑上可能有这些保证:

use std::marker::PhantomData;
use std::rc::Rc;

struct MyNonSend {
    data: i32,
    _not_send: PhantomData<Rc<()>>,  // 标记: 这个类型不 Send (继承了 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