Soundness of mutable access to union containing an atomic

Is the following code sound?

#[repr(C)]
union Ptr {
    raw: *mut (),
    atomic: ManuallyDrop<AtomicPtr<()>>,
}

impl Ptr {
    /// Retrieve the pointer stored, no matter if it was stored with `raw` or `atomic`
    fn get_mut(&mut self) -> &mut *mut () {
        // SAFETY: both union members have exactly the same layout, and 
        // accessing an atomic pointer from a mutable reference does not
        // require any synchronization.
        unsafe { &mut self.raw }
    }
}

miri doesn't seem to see any issue with it, and I would say it's sound, but I'd rather ask to be sure on this topic.

Yes, it is sound; in general, if you have exclusive access to data, then it doesn't matter whether that data has an atomic type or not, because using atomic types only changes whether you can mutate through &.

However, for this purpose you can use AtomicPtr::get_mut() instead of your union and unsafe code. The ManuallyDrop is not doing anything because AtomicPtr has no Drop behavior.

Side note: don't use () when testing. A &mut () doesn't alias any memory and can pass Miri tests which other references will not.

Edit: Nevermind, you used &mut *mut () not &mut ().

OP has a *mut () not &mut () though.

I don't know their use case but what is a better option for a type erased pointer? E.g. If I have something like the classic void* user_data in a C callback API (for the FFI use case, Rust could be on either side of such an API).

EDIT: Having a type erased fat pointer would be nice, just a pointer and a length it is valid for. I don't think a u8 slice is ok for that, for provenance reasons (if there are internal pointers in the pointed to data).

1 Like

Thank you for your answer.
Actually, I'm using an union because it's I've also a vtable, which methods decide if they use the raw pointer or the atomic one. But for by-value methods, I was wondering if I could directly read the pointer before passing it to the virtual method, instead of passing a mutable reference and using AtomicPtr::get_mut in the method. Your answer tell me that I can.

For by-value use, you can call AtomicPtr::into_inner(). Again, the union brings no benefit. It's defining a type-pun that AtomicPtr already supports.

1 Like

That's true, or rather, they dealing with &mut *mut () not &mut ().

I didn't want to go too much in details, but I use a union because I've a generic type with a boolean parameter, and depending on the parameter value (and depending on the vtable), the virtual methods use the raw pointer or the atomic pointer. And when I have a by-value method, I don't need to pass a reference to the virtual method, as I can read the pointer directly, saving an indirection (that was the point of this thread).
I could have just use an AtomicPtr for both cases, but immutable loads would then have to be relaxed, which is not the same as a non-atomic load. That's why I use an union, because when the implementation is not atomic, I can have non-atomic immutable loads.

And when I have a by-value method, I don't need to pass a reference to the virtual method, as I can read the pointer directly, saving an indirection (that was the point of this thread).

If the method is by-value, why can't you use AtomicPtr::into_inner() to perform the read that you would have done with the union?

They are on the machine level.[1] But they may not be at the LLVM level, as LLVM may not be able to optimise them the same. In particular it wouldn't be able to eliminate redundant loads, or merge multiple loads into a larger load. To figure out if that would actually make any difference you would have to look at the assembly though.

Also, relaxed compare and exchange, increment etc is likely different from regular operations. It is just the load and store that are identical on all modern platforms.


  1. Except IA64 which is long dead. And maybe Alpha which is even longer dead. ↩︎

Eliminating redundant loads is indeed an optimization I would like to benefit from, for example if an immutable method is called several times in a row, and the compiler is able to keep the pointer value in register.
The difference is illustrated in this godbolt example: Counter1::incr and Counter2::incr generate the same code, but incr_loop1 and incr_loop2 codes are different.
Non-atomic loads are more optimizable, that's why I want to use them when it's possible (and in my use case, when my generic parameter is false, there is no atomic operation involved anywhere, so i'm using the raw pointer member of my union).

I use a union because of immutable loads, not because of mutable or by-value accesses. Because I've a generic boolean parameter deciding if the implementation use atomic operations or not, and when it's non-atomic, I don't want to pay the cost of relaxed load for immutable accesses.

I've written this thread because in the particular case of a few mutable accesses, e.g. Drop::drop, I know there will be no modification (atomic or not), so I prefer passing the pointer value directly to my virtual method, instead of passing a reference to the atomic pointer, to avoid an unnecessary indirection in the virtual method. So I wanted to know I could soundly read my pointer from a mutable reference.
But again, for immutable accesses, when my generic parameter is false, I want to use the raw pointer member of my union, because there is no atomic operations involved, and non-atomic loads are more optimizable than relaxed loads, as you surely know. I must have one single field type for both values of my generic parameter, hence the union.

Actually, I could do things differently and change the field type depending on the generic parameter value, but that would not be as ergonomic, because of the current compiler limitation regarding generic boolean, so I would have to use a sealed trait and two types implementing this trait, and making the API more complex, because the struct with this generic parameter is already implementing an other sealed trait, etc. So I use a generic boolean parameter, and I have to give my pointer field a type, so I use a union.

You could use AtomicPtr::as_ptr().read() for this purpose. I’ll admit that at that point it’s not pretty, and arguably of no more benefit than the union, but I still think it’s cleaner to avoid a union.

I admit, I didn't think about AtomicPtr::as_ptr. So you're right, I could use AtomicPtr instead of union, but as you said, the benefit is limited, and for now I think the intent is better achieved with the union. I wish the compiler was able to merge exhaustive impls to not have to do this.
Anyway, thank you for your valuable insights.