What are the differences between `*const T` and *mut T` raw pointers?
Asked Answered
C

1

31

I'm writing some unsafe Rust code so I need to know the exact differences between *const T and *mut T. I assumed that it's like &T and &mut T (i.e. you just can't mutate T through &T, period), but that doesn't seem to be the case!

For example, the pointer wrapper NonNull<T> is defined as follows (source):

pub struct NonNull<T: ?Sized> {
    pointer: *const T,
}

However, it's possible to obtain a *mut T from this wrapper via as_ptr, which is just defined as:

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

The function is not even marked as unsafe! I am not allowed to cast from &T to &mut T (for a good reason!), but apparently casting pointers like that is fine.

The Nomicon mentions in the chapter about variance that *const T and *mut T differ in variance:

  • *const T: covariant
  • *mut T: invariant

Is this the only difference between the pointer types? That would seem strange to me...


What exactly are the differences between the pointer types? Are there restrictions for *const T that *mut T doesn't have? If the differences are minimal: what are additional reasons to include both pointer types in the language?

Cleveite answered 13/4, 2019 at 11:17 Comment(3)
Does What are the semantics for dereferencing raw pointers? answer your question?Monochasium
@trentcl That helps a lot, thanks! But I don't think it completely answers my question. For one, that answer doesn't mention variance at all (the one thing, I know for sure is relevant here). I feel like the answer you linked should absolutely be linked in an answer answering this question. But yeah, I don't think it completely answers this question.Cleveite
For one, you can't assign to the dereference of a *const T.Westleigh
P
28

Differences between *const T and *mut T

The main difference between mutable and const raw pointer is, not surprisingly, whether dereferencing them yields a mutable or immutable place expression. Dereferencing a const pointer yields an immutable place expression, dereferencing a mutable pointer yields a mutable one. The implications of mutability according to the language reference are this:

For a place expression to be assigned to, mutably borrowed, implicitly mutably borrowed, or bound to a pattern containing ref mut it must be mutable.

The other difference between const and mutable pointers is the variance of the types, as you already noted, and I think that's all there is.

Casting between mutable and const pointers

You can cast a *const T to a *mut T in safe code, since the difference in mutability only becomes relevant once you dereference the pointers, and dereferencing a raw pointer is an unsafe operation anyway. Without casting to a mutable pointer, you cannot get a mutable place expression for the memory a const pointer points to.

One reason Rust can be a bit more relaxed about mutability for raw pointers is that it does not make any assumptions about aliasing for raw pointers, in contrast to references. See What are the semantics for dereferencing raw pointers? for further details.

Why is NonNull using *const T?

The NonNull pointer type is used as a building block for smart pointers like Box and Rc. These types expose interfaces that follow the usual Rust rules for references – mutation of the pointee is only possible through ownership of or a mutable reference to the smart pointer itself, and a shared reference to the pointee can only be obtained by borrowing the smart pointer itself. This means it is safe for these types to be covariant, which is only possible if NonNull is covariant, which in turn means we need to use a *const T rather than a *mut T.

Why does the language include two different kinds of pointers if they are so similar?

Let's think about the alternative. If there was only a single pointer type, it would necessarily need to be the mutable pointer – otherwise we'd be unable to modify anything through a raw pointer. But that pointer type would also need to be covariant, since otherwise we'd be unable to build covariant smart pointer types. (It's always possible to give up covariance by including a PhantomData<some invariant type> in a struct, but once your struct is rendered invariant by one of its members, there is no way to make it covariant again.) Since mutable references are invariant, the behaviour of this imaginary pointer type would be somewhat surprising.

Having two different pointer types, on the other hand, allows for a nice analogy to references: const pointers are covariant and dereference to immutable place expressions, just like shared references, and mutable pointers are invariant and dereference to mutable place expressions, just like mutable references.

I can only speculate whether these were the actual reasons for the design of the language, since I could not find any discussion on the topic, but the decision doesn't seem unreasonable to me.

Portis answered 14/4, 2019 at 14:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.