What is the real difference between `*const T` and `*mut T` raw pointers?


#1

Hello!

I’d like too know what are the exact differences between *const T and *mut T types, what guarantees they provide and what invariants the programmer must maintain. i find documentation on the topic a bit thin:

  1. Non-exhaustive list of “properties to remember” in Rust book: https://doc.rust-lang.org/book/first-edition/raw-pointers.html
  2. A similar list in the second edition https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html
  3. A bit of important info about variance in nomicon: https://doc.rust-lang.org/nomicon/subtyping.html
  4. Nothing in the reference

Here is what I think is true about *const T and *mut T. Please correct me if I am wrong and confirm, if I am right.

  1. It’s not UB to cast between *const T and *mut T.
  2. *const T is variant and *mut T is invariant over T.
  3. 1) implies that 2) is the single real difference between them.

So if you are using raw pointers to implement a data structure (that is, not for FFI), you can pretty much use any of *const or *mut, provided that variance is correct. You may even have to use *const for “mutable” data if you need variance. The prime example of this is a Unique<T> type, which is logically an owning-raw pointer. It stores *const T to be variant over T, but give access to T via *mut.

If you are using raw pointers for FFI (as parameter and return types of extern “C” functions), then *const vs *mut is purely a question of documenting intent, and does not affect the generated code at all. However, documenting intent is important, because C and C++ have a rule that you can’t mutate a constant object.


#2

Your understanding matches mine, for what it’s worth.

If you are using raw pointers for FFI (as parameter and return types of extern “C” functions), then *const vs *mut is purely a question of documenting intent, and does not affect the generated code at all. However, documenting intent is important, because C and C++ have a rule that you can’t mutate a constant object.

This rule is somewhat reflected on the Rust side, in that &T will coerce to *const T but not to *mut T. The only safe way to get a *mut T from an immutable T is to cast through an intermediate *const T. (Though as you noted, the possibility of this cast means that this difference is only ergonomic.)


#3

Now I have a case where I’m not quite sure which raw pointer to use.

I have a pointer that

  • needs to be passed over language boundaries
  • most likely involves interior mutability
  • is a “subtype” (as in the way you do sub-typing in C, not in Rust) of the actual passed value, as shown in the following snippet
#[repr(C)]
pub struct RawObject {
    f_ptr: unsafe extern "C" fn(*const/mut RawObject, i32) -> i32,
    // the actual pointed-to object has some more fields
}

Should I use *const RawObject or *mut RawObject when passing or storing such a pointer?


#4

The rule of thumb I follow is whether the members of the structure will be mutated by the code that receives that argument. If any writes could occur, requiring a *mut RawObject makes that clear.


#5

The code that receives the argument won’t mutate it, but the it might be mutated by the callback function it contains.


#6

Sounds like the callback deserves a *mut argument, then!


#7

I wonder whether the convention here will eventually just become Option<ptr::NonNull<T>> and *const-vs-*mut will disappear entirely…


#8

@scottmcm probably! I am not sure if you can use Option<NonNull> for ffi though?


#9

You can in some cases! Things like the following are already fairly common:

extern "C" {
    pub fn foo(x: Option<&i32>);
}

#10

Argh, sanitaizer ate my NonNull :slight_smile:

I know that Option by itself is allowed in ffi, but what about NonNull?


#11

Ah, makes sense :slightly_smiling_face: Looks like it trips the lint, at least:

warning: `extern` block uses type `std::option::Option<std::ptr::NonNull<i32>>` which is not FFI-safe: enum has no representation hint
 --> src/lib.rs:5:19
  |
5 |     pub fn bar(x: Option<NonNull<i32>>);
  |                   ^^^^^^^^^^^^^^^^^^^^
  |
  = note: #[warn(improper_ctypes)] on by default
  = help: consider adding a #[repr(...)] attribute to this enum

Its docs do say that "Option<NonNull<T>> has the same size as *mut T", though, so it’d almost work…

Ah, there’s an issue about it: