Rust and C++ have very different views on how thread-safety works and how it is documented: a C++ method may be thread-safe or -unsafe independent of its const-ness, and it all basically depends on just documentation. In Rust the information is in the type system with Send and Sync.
In a world where C++ class instances are regularly passed into Rust, it falls upon the Rust bindings to divine whether that object can be sent to a foreign thread (Send), and which of its methods are Sync. It is also not necessarily clear if thread-safe methods can be expressed in Rust as taking &self depending on the field declarations:
class Foo {
Mutex mutex_;
// static_ is here to make it impossible for Rust to declare a data_: CppMuted<*mut c_void>
uint32_t static_;
void *data_;
void thread_safe_set(void *data) {
auto guard = mutex_.lock();
data_ = data;
}
}
struct Foo {
mutex_: CppMutex,
static_: u32,
data_: *mut c_void,
}
extern "C" {
fn lib__Foo__thread_safe_set(this: *mut Foo, data: *mut c_void);
}
impl Foo {
#[inline(always)]
fn thread_safe_set(&self, data: *mut c_void) {
unsafe { lib__Foo__thread_safe_set(ptr::from_ref(self).const_cast(), data) }
}
}
The &self here promises to Rust that both static_ and data_ stay unchanged, but the FFI call actually mutates data_: on the Rust side we should be wrapping data_ in an SyncUnsafeCell but that is generally impossible to figure out from just the header declarations.
Rust and C++ have very different views on how thread-safety works and how it is documented: a C++ method may be thread-safe or -unsafe independent of its const-ness, and it all basically depends on just documentation. In Rust the information is in the type system with
SendandSync.In a world where C++ class instances are regularly passed into Rust, it falls upon the Rust bindings to divine whether that object can be sent to a foreign thread (
Send), and which of its methods areSync. It is also not necessarily clear if thread-safe methods can be expressed in Rust as taking&selfdepending on the field declarations:The
&selfhere promises to Rust that bothstatic_anddata_stay unchanged, but the FFI call actually mutatesdata_: on the Rust side we should be wrappingdata_in anSyncUnsafeCellbut that is generally impossible to figure out from just the header declarations.