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.
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…
You can in some cases! Things like the following are already fairly common:
extern "C" {
pub fn foo(x: Option<&i32>);
}
Argh, sanitaizer ate my NonNull
I know that Option by itself is allowed in ffi, but what about NonNull?
Ah, makes sense 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
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.
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).
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.
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.
“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.
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.