为什么Rust允许对不可变的RwLock进行写操作?

5
假设我们声明了一个不可变(非let mut)的RwLock实例,如下所示:
let value = RwLock::new(0);

由于value是不可变的,我原以为我无法更改RwLock的内部值。但是当我进行测试时,显然这是有效的:

{
  *value.write().unwrap() = 5;
}

我在想我是否错误地使用了RwLock,这种情况不应该发生(如果是这样的话,我担心锁可能无法按预期工作)。然而,我确信这种行为背后有一些解释,因为Rust在涉及可以更改什么和不能更改什么方面非常明确。

我的猜测是RwLock将其内部值存储在堆上,因此它只需要跟踪对该值的不可变指针。因此,每当我们写入该值时,RwLock结构本身将保持不变,因为指针不会改变。

这只是一个猜测,可能是错误的。如果有人愿意纠正我,我会非常高兴。


澄清一下:我知道 Reader-Writer 锁应该如何工作。我的问题不在于同步机制,而是为什么 Rust 在处理不可变性时不像对待其他值一样处理 RwLock 值。它是否是编译器处理的“神奇”类型之一,还是有其他我不知道的原因。
1个回答

8

因为在编译器看来,你没有改变 RwLock

RwLock::write() 接收一个 &self 作为接收器,并返回一个 RwLockWriteGuard,其中 DerefMut-impl 用于执行赋值操作 *value... = 5。这两个步骤之间发生了什么取决于 RwLock 的实现。

由于您特别询问的不是关于RwLock本身,而是“魔法”的存在或不存在:在不可变(&)或可变(&mut)引用方面,这里绝对没有任何神奇的地方。实际上,可能有一些误解:
虽然我们称它们为“不可变”或“可变”(因为它们是这样使用的),但最好将它们视为“共享”和“独占”引用。使用独占引用(&mut),您可以随时自由地对值进行修改,而不会导致别名重叠的同时发生突变;编译器可以保证这种(特殊的)用例。 同样,对于共享引用(&),您可以将该值作为别名,因为不能进行突变。编译器也可以保证这个(特殊的)情况。
虽然这涵盖了令人惊讶的大量情况,但对于RwLock来说,这既不可能也不可取。RwLock使用其内部锁定机制(编译器无法知道)来确定是否应允许突变,并保持突变别名不会发生的保证。这就是为什么RwLock可以从&self到突变内部值的原因。

对于RefCell类型也是如此。

5
删除我写了一半的答案,因为这个更清晰,但是 OP 可能想看看内部可变性文档以获取这个通用模式。 - 2e0byo
3
不错的回答,但说“绝对没有任何魔法参与”并不完全正确——通常不可能从&引用到同一数据的&mut引用。允许这样做的魔法在于UnsafeCell,它被编译器特殊处理,并作为内部可变性的基础,包括原子操作在内的所有其他东西。如果没有这个魔法,即使在不安全的代码中也无法实现内部可变性。 - user4815162342

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接