为什么 Mutex 包含一个 Box?

Why does Mutex contain a Box?

Rust 的 std::sync::Mutex 是一个包含堆分配 inner 互斥量的结构,以及这个半神秘的注释:

pub struct Mutex<T: ?Sized> {
    // Note that this mutex is in a *box*, not inlined into the struct itself.
    // Once a native mutex has been used once, its address can never change (it
    // can't be moved). This mutex type can be safely moved at any time, so to
    // ensure that the native mutex is used correctly we box the inner mutex to
    // give it a constant address.
    inner: Box<sys::Mutex>,
    poison: poison::Flag,
    data: UnsafeCell<T>,
}

注释解释说Box是用来给内部互斥体一个稳定的地址。但我似乎找不到任何解释为什么首先需要稳定地址。

至少在类 Unix 平台上,这里的 "native mutex" (sys::Mutex) 最终是 libc::pthread_mutex_t (source code) 的包装器。

在 C 语言中,制定禁止移动互斥量的规则几乎是有意义的,因为互斥量是通过指针使用的,在有指向它的活动指针时移动互斥量显然是错误的。但是在 Rust 中,你甚至不能尝试移动某些东西,除非 没有对它的实时引用。所以这种说法似乎没有说服力

为什么原生互斥量必须有一个稳定的地址?

我认为这里的答案基本上只是“pthread_mutex_t 是一种不透明类型”。 POSIX 标准不保证它是可移动的,所以我们不能对它做任何假设。来自 Rust 库团队的 Mara Bos discussed this in a Twitter thread.

C 类型可能无法移动的原因有很多。它可能包含一个自引用指针,即它的一个字段可能指向另一个字段。这在 Rust 中通常是不允许的,但在 C 和 C++ 中是允许的,并且一些现实世界的类型将此作为优化。 (例如,GCC 对 std::string does this 的实现。)C 类型可能做的另一件事是将指向自身的指针插入到某个全局注册表中。在任何一种情况下,移动或复制此类类型的实例都会导致悬空指针和其他不一致。

pthread_mutex_t 的某些(大多数?)实现不会执行这些操作。但是 POSIX 标准并不禁止它们,并且任何给定的实现都可以在将来 没有警告的情况下开始做这些事情。 Rust 标准库必须对此进行防御,这就是为什么 Mutex 在 Unix 上总是需要 Box 的原因。 (请注意 Mutex 自从写完这个问题后发生了一些变化。它不再包含 Windows 上的 Box。)

顺便说一句,futex 系统调用(pthread_mutex_t 在 Linux 上的实现方式)也将锁的地址作为参数。这种事情是 pthread_mutex_t 通常不可移动的另一个原因。然而,这个特殊问题与 Rust 无关,因为 Rust 中的 Mutex 总是在锁定时被借用,这意味着它只能在解锁时移动。