Shouldn't pointers be Send + Sync? Or


#1

It sounds to me raw pointers not being Send + Sync is weird. Here is why:

  1. It is safe to send or share them. unsafe is to dereference them. And this is unsafe even when not shared between threads.
  2. One can circumvent that very easily with a few casts.
  3. Why is AtomicPtr: Send + Sync then? One may store a pointer to a local variable on an AtomicPtr shared between threads and then another thread could load such pointer and dereference it and… Well, read item 1.

Explanation:

  1. Supposing they are Send:
let var = 5;
let ptr = &var as *const i32;
let thread = thread::spawn(move || {
    println!("{:?}", ptr);
}

There is no problem in such code. The problem comes if the program dereferences such pointer, which is unsafe.

  1. Working around this restriction:
let var = 5;
let ptr = &var as *const i32 as usize;
let thread = thread::spawn(move || {
    println!("{:?}", ptr as *const i32);
}
  1. I see no difference in the between:
let var = 5;
let ptr = &var as *const i32;
let thread = thread::spawn(move || {
    println!("{}", unsafe { *ptr });
}

And

let var = 5;
let mut ptr = AtomicPtr::new(&var as *const i32);
let thread = thread::spawn(move || {
    println!("{}", unsafe { **ptr.get_mut() });
}

#2

this would be a breaking change, as I rely on raw pointers to make my stuff !Send and !Sync

AtomicPtr is an atomic, all atomics are Send + Sync


#3

Yes, I don’t think we can go back now. It would break everything. This more like an alarm.

And I don’t think it’s consistent Send + Sync for AtomicPtr<T> and not for *mut T. The operations of &AtomicPtr<T> over *mut T are atomic, but not the operations of *mut T over T.


#4

You are right. The lack of these impls for raw pointers doesn’t make any code more sound, because unsafe is already required to dereference them.

The reasons why pointers implement neither of these traits is basically as a lint. The majority of people writing rust almost never think about Send and Sync because they almost never have to. And a very substantial portion of people using raw pointers are going to be doing so for FFI purposes.

It’s possible that those pointers obtained through C APIs cannot be safely used across threads in the manner that the rust code wants to use them. Hence, when somebody writes a struct around some FFI pointer:

struct CApiThing {
    ptr: *mut c_void,
}

the fact that CApiThing does not automatically impl Send or Sync can prevent some footguns, and forces the author to explicitly think about it before providing these impls.