`*const T` 和 `*mut T` 原始指针有什么区别?

28
我正在编写一些不安全的Rust代码,所以我需要知道*const T*mut T之间的确切区别。我认为它就像&T&mut T一样(即你只是不能通过&T修改T),但事实并非如此!
例如,指针包装器NonNull<T>的定义如下(源代码见这里):
pub struct NonNull<T: ?Sized> {
    pointer: *const T,
}

然而,通过此包装器可以通过as_ptr获得*mut T,该方法定义如下:

pub const fn as_ptr(self) -> *mut T {
    self.pointer as *mut T
}

该函数甚至未被标记为unsafe!我不允许从&T强制转换为&mut T(有很好的理由!),但显然像这样转换指针是可以的。
Nomicon在有关变异的章节中提到:*const T*mut T在变异上存在差异:
  • *const T:协变
  • *mut T:不变
这是指针类型之间的唯一区别吗?这对我来说听起来很奇怪…
指针类型之间到底有哪些差异? *const T是否有*mut T没有的限制?如果差异很小:使用语言中的两种指针类型的其他原因是什么?

1
解引用裸指针的语义是什么? 这个问题的回答是否对您有帮助? - trent
@trentcl 非常感谢您的帮助!但我认为它并没有完全回答我的问题。首先,那个答案根本没有提到方差(这是我确定与此相关的唯一一件事)。我觉得您提供的链接应该绝对被包含在回答这个问题的答案中。但是,是的,我认为它并没有完全回答这个问题。 - Lukas Kalbertodt
1
首先,您无法分配给*const T的反引用。 - Francis Gagné
1个回答

23

*const T*mut T的区别

可变和常量裸指针的主要区别在于,不出所料,它们解引用后是否产生可变或不可变的位置表达式。解引用常量指针会产生一个不可变的位置表达式,解引用可变指针会产生一个可变的位置表达式。根据语言参考中的可变性规定,可变性的含义如下:

对于要分配、借用、隐式借用或绑定到包含ref mut模式的地方表达式,必须是可变的。

常量和可变指针之间的另一个区别是类型的协变性,正如您已经注意到的那样,我认为这就是所有的区别了。

在可变和常量指针之间进行转换

在安全代码中,您可以将*const T转换为*mut T,因为只有在解引用指针时可变性的区别才变得相关,而解引用原始指针本身就是一种不安全的操作。如果不将其转换为可变指针,则无法为常量指针指向的内存获得可变位置表达式。

Rust之所以对裸指针的可变性要放松一些,是因为它对裸指针的别名没有任何假设,与引用不同。有关详细信息,请参见什么是解引用裸指针的语义?

NonNull为什么使用*const T

NonNull指针类型用作智能指针(如BoxRc)的构建块。这些类型公开的接口遵循通常的Rust引用规则——只有通过拥有或可变引用智能指针本身才能对指针进行修改,并且只能通过借用智能指针本身来获得指针的共享引用。这意味着对于这些类型来说,它们可以是协变的,只有在NonNull是协变的情况下才可能,这反过来又意味着我们需要使用*const T而不是*mut T

如果它们如此相似,为什么语言中包含两种不同类型的指针?

让我们考虑一种替代方案。如果只有一个指针类型,它必须是可变指针——否则,我们将无法通过原始指针修改任何内容。但是,该指针类型也需要协变性,否则我们将无法构建协变的智能指针类型。(在结构体中包含 `PhantomData<某个不变类型>` 可以放弃协变性,但是一旦您的结构体被其中一个成员渲染成不变的,就没有办法再使其协变了。)由于可变引用是不变的,因此这种想象中的指针类型的行为会有些令人惊讶。
另一方面,有两种不同的指针类型可以很好地类比于引用:const 指针是协变的,解引用为不可变的位置表达式,就像共享引用一样;而可变指针是不变的,解引用为可变的位置表达式,就像可变引用一样。
我只能猜测这是否是设计语言的实际原因,因为我找不到任何关于这个主题的讨论,但是这个决定对我来说看起来并不荒谬。

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