Skip to content

Thread-safety in a mixed Rust/C++ code base #73

Description

@aapoalas

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    l-rustChanges that are mainly in Rustp-safetySafety or soundness related problems or use casesp-semanticsLanguage semantics differencessolved-crubitSolved by Crubitt-problem-statementA description of an interop problem

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions