Sendness of NonNull versus Sendness of AtomicPtr and Unique


#1

I would like to understand why NonNull is not Send, but AtomicPtr is and Unique is.
I know that the issue of NonNull vs Unique is the aliasing. But I don’t know how AtomicPtr prevents aliasing. And I don’t understand quite well why aliasing of pointers is bad when sending it to another threads. But, remember, atomicity of AtomicPtr is only useful when we have &AtomicPtr<T> or Arc<AtomicPtr<T>> or something similar.


#2

Given *mut T is !Send, it makes sense for NonNull<T> to also be !Send - it just adds an “assertion” that the ptr is not null, but is otherwise analogous to *mut T. Aliasing doesn’t come into play with NonNull<T> any more than with *mut T, which is to say aliasing doesn’t apply at all here because these are raw pointers. *mut T isn’t Send mostly as a lint - author should pause and think of the implications of sending the raw ptr.

Unique<T> also has a non-null raw ptr around it, but this type is a basic primitive to build runtime ownership checks. It’s also intended to store raw ptrs to Rust types, whereas NonNull is more general - it can wrap a FFI ptr.


#3

Ok, but… Why is AtomicPtr Send? I mean, it is just an wrapper for atomic loading and storing a memory address, and not the memory inner value, right? Loading and storing an AtomicPtr uses a *mut T.


#4

It would be useless if it weren’t Send (or Sync for that matter). Like Unique<T>, it’s also intended to store Rust types internally. It also, of course, adds the atomic operations.

So while these 3 all have a *mut T inside, I think their intended semantics and use cases are different. And given that *mut T is not Send by default, whenever you define a type that wraps one you need to decide whether Send is applicable. I suspect NonNull<T> only wants to ascribe the non-nullness aspect, and nothing else.

But this is just my interpretation.


#5

Maybe I am focusing too much in NonNull. My question is about both raw and NonNull. What I mean is: it is very easy to alias an AtomicPtr:

let mut x = 5;
let p = AtomicPtr::new(&mut x as *mut _);
let q = AtomicPtr::new(p.load(SeqCst));

#6

Yeah, it’s a low-level API meant more of as a building block. Not only can you end up creating aliases, but you can also send it to another thread and end up pointing at a bogus memory location if this thread moves on or exits.

You can think of it like Unique, which you can also use to obtain raw ptrs and create aliases. The idea is higher-level code builds on top of it, and ensures that Rust’s aliasing violations (i.e. once references are formed from the raw ptr) are not encountered. AtomicPtr is similar except it also gives you atomic operations that you can leverage while building the higher level APIs.