一个 trait 声明如下:

pub trait SomeTrait: Send{
                    // ^ super trait
    fn some_method(&self);
}

Copy

pub trait Copy: Clone { }

若一个类型实现了 Copy,则:

  • 此类型可以通过 memcpy 进行拷贝。

  • 此类型是拷贝语义

由于 Copy 本身是一个 safe trait。因此只有 POD 类型才能实现 Copy trait。

移动语义和拷贝语义都是通过 memmove 实现的,它们唯一的区别在于赋值后是否还能访问被复制的变量。当然,这种操作经常会被优化掉。

要实现 Copy 可以使用 derive 的方式或者是手动实现,但是它们略有不同:

#[derive(Clone)]
struct MyStruct<T>(T);
// same as struct MyStruct<T: Copy>(T);

impl<T: Copy> Copy for MyStruct<T> { }

derive 会对类型 T 也施加 Copy 限制。而 impl 的形式则允许 T 没有实现 Copy。

若一个类型的所有成员都实现了 Copy,则这个类型可以实现 Copy。对于引用类型而言,&T 实现了 Copy,但是 &mut T 没有实现 Copy。

实现了 Drop 的类型不能实现 Copy。

除了上面的情况外,下面类型也实现了 Copy:

  • 函数项(Function item)类型。

  • 函数指针类型(fn() → i32

  • 闭包类型。若没有捕获值,或者捕获的值都实现了 Copy,则闭包类型也会实现 Copy。

Send 和 Sync

pub unsafe auto trait Send { }
pub unsafe auto trait Sync { }

Send 和 Sync 是两个 unsafe 的 auto 类型。这意味着:

  • 若类型的所有成员都实现了 Send,则此成员自动实现 Send。

  • 手动实现 Send 是一个 unsafe 行为。

如果不需要编译器自动实现的 Send/Sync,则可以手动取消:

#![feature(negative_impls)]

// I have some magic semantics for some synchronization primitive!
struct SpecialThreadToken(u8);

impl !Send for SpecialThreadToken {}
impl !Sync for SpecialThreadToken {}
  • 若类型实现了 Send,则可以安全地将其移动到另一个线程。

  • 若类型实现了 Sync,则可以安全地在多个线程之间共享。(当且仅当 &T 是 Send 时类型 T 才是 Sync)

Rc 没有实现 Send,因为引用计数是共享的,且没有同步。因此你无法将一个 Rc 值移动到另一个线程:

use std::rc::Rc;
fn main() {
    let rc = Rc::new(42);
    std::thread::spawn(move || {  // ERROR: rc 没有实现 Send
        println!("{}", rc);
    });
}

同样类似的还有 MutexGuard,这是因为在一个线程获取锁,在另一个线程释放是未定义行为。结合 async 来看,就是 MutexGuard 无法跨越 await 点。

这里注意 parking_lot 为 MutexGuard 实现了 Send,这可能会导致未定义行为。

若类型 T 没有实现 Sync,则 &T 无法在多个线程之间共享。UnsafeCell/Cell/RefCell 由于可以通过 &T 修改变量,因此没有实现 Sync。

常见同步原语

Arc

Arc(Atomic Ref Counter) 要求类型实现 Send 和 Sync:

unsafe impl<T: Sync + Send> Send for Arc<T> {}
unsafe impl<T: Sync + Send> Sync for Arc<T> {}

Arc 要求类型 T 本身一定已经是线程安全了的。Arc 只是允许在多个线程之间共享值。

考虑 Arc<RefCell<T>> 由于 RefCell<T> 没有实现 Sync,因此 Arc 也不会实现 Sync。

Mutex

Mutex 使用互斥原语保护共享数据:

unsafe impl<T: Send> Send for Mutex<T> {}
unsafe impl<T: Send> Sync for Mutex<T> {}

Mutex 可以将一个实现了 Send 的值转为 Send + Sync

考虑 Mutex<Rc<T>> 由于 Rc 本身没有实现 Send,可能出现下面的情况:

use std::{rc::Rc, sync::Mutex};

fn main() {
    let rc = Rc::new(42);
    let mrc = Mutex::new(rc.clone());

    std::thread::spawn(|| {
        println!("{}", mrc.lock().unwrap());
                    // ^ Mutex<Rc<T>> 未实现 Send。
    });
}

Mutex 和 MutexGuard 一起使用:

impl<T> !Send for MutexGuard<'_, T> {}
unsafe impl<T: Sync> Sync for MutexGuard<'_, T> {}

MutexGuard 未实现 Send,这意味着 MutexGuard 无法发送到另一个线程。这是因为在一个线程加锁,另一个线程解锁是未定义行为。

MutexGuard 实现了 Sync,这是因为 &T 可以被其它线程读取。

parking_lot 的 MutexGuard 没有实现 !Send,因此允许跨线程使用。在 async 的情况下允许持锁跨越 await 点,这本身是不安全的。

RwLock

读写锁允许细化锁的使用:

unsafe impl<T: Send> Send for RwLock<T> {}
unsafe impl<T: Send + Sync> Sync for RwLock<T> {}

由于 RwLock 允许多个线程读和单个线程写。其中 Send 保证值可以在多个线程之间转移,而 Sync 则保证多个线程可以同时读。

RwLockReadGuard 和 RwLockWriteGuard 和 MutexGuard 的约束相同。

内部可变性

除了常见的锁外,还有一些内部可变性用于单线程操作:UnsafeCell, Cell 和 RefCell。

UnsafeCell

#[repr(transparent)]
pub struct UnsafeCell<T: ?Sized> {
    value: T,
}

由于施加了 repr(transparent),因此 &mut T 可以转为 &mut UnsafeCell<T>

UnsafeCell 本身没有做任何工作,它的可变性基于一个 unsafe 操作:

pub const fn get(&self) -> *mut T {
    self as *const UnsafeCell<T> as *const T as *mut T
}

当然,这个函数是 safe 的,但是解引用是 unsafe 的。

Cell

pub struct Cell<T: ?Sized> {
    value: UnsafeCell<T>,
}

Cell 通过 std::mem::replace 实现内部可变性。这依赖于下面几个方法:

impl<T> for Cell<T> {
    pub fn set(&self, val: T) {
        self.replace(val);
    }
    pub const fn replace(&self, val: T) -> T {
        // SAFETY: This can cause data races if called from a separate thread,
        // but `Cell` is `!Sync` so this won't happen.
        mem::replace(unsafe { &mut *self.value.get() }, val)
    }
}

impl<T: Copy> for Cell<T> {
    pub const fn get(&self) -> T {
        // SAFETY: This can cause data races if called from a separate thread,
        // but `Cell` is `!Sync` so this won't happen.
        unsafe { *self.value.get() }
    }
}

对于实现了 Default trait 的类型,还允许通过 take 获取值:

impl<T: Default> for Cell<T> {
    pub fn take(&self) -> T {
        self.replace(Default::default())
    }
}

根据上面我们可以认为 Cell 的开销只是一次内存拷贝。

RefCell

RefCell 实现了运行时借用规则:

type BorrowFlag = isize;

pub struct RefCell<T: ?Sized> {
    borrow: Cell<BorrowFlag>,
    value: UnsafeCell<T>,
}

在访问值的时候 RefCell 依赖于 borrow 和 borrow_mut。若在 borrow 的情况下获取 borrow_mut,则会导致程序 panic:

impl<T> for RefCell<T> {
    pub fn borrow(&self) -> Ref<'_, T> {
        match self.try_borrow() {
            Ok(b) => b,
            Err(err) => panic_already_mutably_borrowed(err),
        }
    }

    pub fn borrow_mut(&self) -> RefMut<'_, T> {
        match self.try_borrow_mut() {
            Ok(b) => b,
            Err(err) => panic_already_borrowed(err),
        }
    }
}

RefCell 对应的多线程版本是 RwLock。由于 BorrowFlag 只是一个 isize, 因此 RefCell 的开销也很小。

OnceCell

OnceCell 只允许写一次值:

pub struct OnceCell<T> {
    // Invariant: written to at most once.
    inner: UnsafeCell<Option<T>>,
}

impl<T> for OnceCell<T> {
    pub fn set(&self, value: T) -> Result<(), T> {
        match self.try_insert(value) {
            Ok(_) => Ok(()),
            // 若已经有值了,就见 set 提供的 value 返回。
            Err((_, value)) => Err(value),
        }
    }
}

OnceCell 的多线程版本是 OnceLock。

LazyCell

LazyCell 在创建时提供一个初始化函数,在访问时无值才会进行初始化:

enum State<T, F> {
    Uninit(F),
    Init(T),
    Poisoned,
}

pub struct LazyCell<T, F = fn() -> T> {
    state: UnsafeCell<State<T, F>>,
}

当 LazyCell drop 时会将 state 转为 Poisoned。

LazyCell 的多线程版本是 LazyLock。

Last moify: 2025-03-27 01:47:17
Build time:2025-12-15 13:00:34
Powered By asphinx