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

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.

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

1 Like

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

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

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

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

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

Argh, sanitaizer ate my NonNull :slight_smile:

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

1 Like

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:

Today I’ve stumbled on this PR, which suggest that using *const where *mut is expected might be unsound (at least, that’s what relnotes say?)

Is this really unsound though? In my current view of the world, this is not unsound (by itself). What matters is how did you get that pointer. If you originally had an &mut __m256i then using *const or *mut should be OK. If you originally had an &__m256i, then both are unsound.

cc @RalfJung :slight_smile:

3 Likes

My thoughts on that:

  • It takes quite a lot for an unsafe fn to be considered unsound!
  • Assuming that variance is the only difference, it’s irrelevant here because __m256i is not generic.
  • The fact that &T coerces to *const T does make the signature surprising and dangerous.

Unless there’s something peculiar about SIMD types that I don’t know about, I wouldn’t take that PR and its relnotes too literally; I feel the author may have used the word “unsound” too hastily.

2 Likes

(See also, apparently people really don’t like the idea of Option<NonNull<T>> for FFI.)

2 Likes

Is this really allowed?

Yes. Optional reference is ffi safe (documented here https://doc.rust-lang.org/nomicon/other-reprs.html) and i32 is ffi safe (currently undocumented https://github.com/rust-lang-nursery/nomicon/issues/23, but, for example, c_int is defined as i32 on some platforms: https://docs.rs/libc/0.2.47/libc/type.c_int.html).

2 Likes

I think you missed the other distinction between *const and *mut, which is that *p = ... works on one but not on the other. Personally, I find that to be very useful. I’m glad that it’s not UB to cast between them. The UB should happen when writing to memory that’s read-only, not when casting the pointer.

I agree. *const T and *mut T are equivalent in terms of UB.

However, as @ExpHP wrote

Using *const T could lead to people thinking they can pass in an &T, and that would be UB.

4 Likes

So may I ask a question.

*const UnsafeCell<T> is isomorphic to *mut T for sure. Are they just the same?

(if you turn &T into *const UnsafeCell<T> you also get UB, so use *const UnsafeCell<T> in signature is not that dangerous)

*const T and *mut T are both equivalent to usize from a safety point of view. This is easy to see when you can cast any random integer into a raw pointer using only safe code.

Pointers are complicated, and much more than a number. Pointers are both the actual (well, virtual) address of memory, as well as context about how that pointer is intended to be used: that is the thesis of the work @RalfJung has worked on with their “Aliased Borrows” model and MIRI. TL;DR we aren’t completely sure what the model is yet.

1 Like

"isomorphic" in which sense?

Raw pointers have pretty much no guarantees attached to them. So you can do almost anything. But that's not a useful statement, because all it means is that you have to be careful when accessing a raw pointer.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.