What we are preventing with the Send trait?

Hi All Again :slight_smile:

my rust is definitely not good yet, and I am not understanding why the following code doesn't work.

I have a type like this:

pub struct RawConnection {
    db: *mut ffi::sqlite3,
} 

After I generate this struct I want to pass it to a different thread and never touch it again in the original thread.

match sql::open_connection(String::from(":memory:")) {
    Ok(rc) => { // <- of type RawConnection

    let (tx, rx) = channel();
    let db = DBKey { tx: tx };

    thread::spawn(move || {
        listen_and_execute(rc, rx);
    });
    ...

This code doesn't compile:

  │error[E0277]: the trait bound `*mut sqlite::ffi::sqlite3: std::marker::Send` is not satisfied in `[closure@src/lib.rs:377:43: 379:30 rc:sqlite::Raw
  │Connection, rx:std::sync::mpsc::Receiver<Command>]`
  │   --> src/lib.rs:377:29
  │    |
  │377 |                             thread::spawn(move || {
  │    |                             ^^^^^^^^^^^^^ within `[closure@src/lib.rs:377:43: 379:30 rc:sqlite::RawConnection, rx:std::sync::mpsc::Receiver<C
  │ommand>]`, the trait `std::marker::Send` is not implemented for `*mut sqlite::ffi::sqlite3`
  │    |
  │    = note: `*mut sqlite::ffi::sqlite3` cannot be sent between threads safely
  │    = note: required because it appears within the type `sqlite::RawConnection`
  │    = note: required because it appears within the type `[closure@src/lib.rs:377:43: 379:30 rc:sqlite::RawConnection, rx:std::sync::mpsc::Receiver<
  │Command>]`
  │    = note: required by `std::thread::spawn`

Complaining that the original Structure does not implement the trait Send, which is correct.
However I do not understand what problem we are trying to prevent.

Maybe if we move between thread the a stack reference when the element refered in the stack goes out of scope (get "free-ed") we will be pointing to an area of memory out of our control?

My particular structure contains a pointer to an area of memory in the heap, am I safe to unsafetly implement the Send trait?

unsafe impl Send for RawConnection {}

1 Like

There's some discussion in the nomicon: Send and Sync - The Rustonomicon

In the case of an FFI pointer, you need to actively decide if that type is really legal to send between threads. It may be fine in many cases, but the FFI library might have some thread restrictions.

2 Likes

Hi @cuviper

sure but what are the cases where it is not legal? Stack allocation? Others?

Best,

Simone

For example, the pointer might point to data on the local thread stack, which might later vanish. Sending it to another thread will not send the data along.

That doesn't mean it happens in this case, but it might.

2 Likes

Stack allocation is not inherently bad for Send. It mostly just indicates that you ought to have a lifetime attached to this. std::thread::spawn requires 'static lifetime, but there are other crates that have options for threading with reduced scopes.

I can't enumerate all possibilities why a pointer might not be Send, but a few examples:

  • It uses some data in thread-local storage, and would see something different when sent to a different thread.
  • Perhaps the pointer refers to some shared data in the FFI library, which might have other live pointers too, and the library is not synchronized to allow simultaneous access in different threads.
  • It may have internal mutability that's not thread-safe, much like the simple reference counts of Rc vs. the atomic updates in Arc.
  • etc. -- mostly concerned with data races, I suppose.
5 Likes

An example of where you don't want Send that immediately springs into my mind is OpenGL. OpenGL represents objects with GLint's that are associated with the current context, which is thread local (although the context can be unset, and set to another thread, let's keep it simple here). If you wanted to write some wrappers you could use this:

struct GlTexture(GLint);
impl !Send for GlTexture {}

Otherwise you could send the int to another thread... which would not work, or worse modify some other texture in another context that was not intended.
(It would also cause problems if you tried using it after the GL context make non-current. So I guess a lifetime specifier might be necessary as well)

Originally when Rust had a Garbage collector, the GC Heap was local to the current thread, which meant all GC pointers had to be not Send.

3 Likes

Hi @cuviper , @trissylegs

thanks for your answer, now it is clearer.

Best :slight_smile:

2 Likes