10

I have a scenario where Rust will call C to malloc a buffer and stash the resulting pointer into a struct. Later on, the struct will be moved to a thread and passed to a C function which mutates it.

The naive approach to my problem looks like this (playground):

extern crate libc;

use libc::{c_void, malloc, size_t};
use std::thread;

const INITIAL_CAPACITY: size_t = 8;

extern "C" {
    fn mutate(s: *mut Storage);
}

#[repr(C)]
struct Storage {
    #[allow(dead_code)]
    buf: *mut c_void,
    capacity: usize,
}

fn main() {
    let buf = unsafe { malloc(INITIAL_CAPACITY) };
    let mut s = Storage {
        buf: buf,
        capacity: INITIAL_CAPACITY,
    };
    thread::spawn(move || {
        unsafe {
            mutate(&mut s); // mutates s.val, maybe reallocates it, updating s.capacity if so.
        }
    }).join()
        .unwrap();
}

Gives:

error[E0277]: the trait bound `*mut libc::c_void: std::marker::Send` is not satisfied in `[closure@src/main.rs:26:19: 30:6 s:Storage]`
  --> src/main.rs:26:5
   |
26 |     thread::spawn(move || {
   |     ^^^^^^^^^^^^^ `*mut libc::c_void` cannot be sent between threads safely
   |
   = help: within `[closure@src/main.rs:26:19: 30:6 s:Storage]`, the trait `std::marker::Send` is not implemented for `*mut libc::c_void`
   = note: required because it appears within the type `Storage`
   = note: required because it appears within the type `[closure@src/main.rs:26:19: 30:6 s:Storage]`
   = note: required by `std::thread::spawn`

Which is the compiler's way of saying that because a *mut c_void doesn't implement Send, neither does Storage so you can't move it into the thread closure.

I thought that using a Unique pointer might solve this. Let's try it (playground):

#![feature(ptr_internals)]
extern crate libc;

use libc::{c_void, malloc, size_t};
use std::ptr::Unique;
use std::thread;

const INITIAL_CAPACITY: size_t = 8;

extern "C" {
    fn mutate(s: *mut Storage);
}

#[repr(C)]
struct Storage {
    #[allow(dead_code)]
    buf: Unique<c_void>,
    capacity: usize,
}

fn main() {
    let buf = Unique::new(unsafe { malloc(INITIAL_CAPACITY) }).unwrap();
    let mut s = Storage {
        buf: buf,
        capacity: INITIAL_CAPACITY,
    };
    thread::spawn(move || {
        unsafe {
            mutate(&mut s); // mutates s.val, maybe reallocates it, updating s.capacity if so.
        }
    }).join()
        .unwrap();
}

But this gives:

warning: `extern` block uses type `std::ptr::Unique<libc::c_void>` which is not FFI-safe: this struct has unspecified layout
  --> src/main.rs:11:18
   |
11 |     fn mutate(s: *mut Storage);
   |                  ^^^^^^^^^^^^
   |
   = note: #[warn(improper_ctypes)] on by default
   = help: consider adding a #[repr(C)] or #[repr(transparent)] attribute to this struct

Is there a way to have the Storage struct both implement Send and have mutable pointers to its instances be FFI safe?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Edd Barrett
  • 3,425
  • 2
  • 29
  • 48
  • 4
    Why not just implement `Send`? – Shepmaster May 09 '18 at 17:06
  • 1
    Highly related: [How can I guarantee that a type that doesn't implement Sync can actually be safely shared between threads?](https://stackoverflow.com/q/36649865/155423) – Shepmaster May 09 '18 at 17:07
  • Morning shepmaster! I actually didn't know that you could implement `Send` manually. As I understand it (from reading the question you linked) I should be able to do a `unsafe impl Sync for Storage {};`. Then the compiler is trusting me that the pointer is not mutably shared elsewhere. Is that correct? – Edd Barrett May 10 '18 at 09:16
  • Is that the only way I can achieve what I'm looking for? If there's a way to do it without `unsafe` I'd probably favour that. Thanks. – Edd Barrett May 10 '18 at 09:18
  • I meant `unsafe impl Send for Storage {};`. Here's a link to the playground where that code does indeed compile: https://play.rust-lang.org/?gist=721f662f26a3aaf9ec4f209143b547d4&version=nightly&mode=debug – Edd Barrett May 10 '18 at 09:59

1 Answers1

12

By default Rust assumes *mut T is not safe to send between threads, and this means structs containing it are not safe either.

You can tell Rust that it is safe indeed:

unsafe impl Send for Storage {}

It relies entirely on your knowledge of how C uses data behind this pointer. Implementing Send means C won't rely on thread-local storage or thread-specific locks when using the object behind this pointer (paradoxically, that's true for most "thread-unsafe" C code).

It doesn't require C to handle access from multiple threads at once — that's what Sync is for.

Kornel
  • 97,764
  • 37
  • 219
  • 309
  • So, I am taking into my own hands t ensure that what the pointer points to still lives at any time to containing struct lives? – Edd Barrett May 16 '18 at 11:29
  • 2
    Yes, lifetime of raw pointers is not checked by Rust. They have all the freedom, and potential crashes, of C pointers. See `ptr`'s `as_ref` and `PhantomData` for adding more type safety around wrapper structs. That's separate from `Send`/`Sync`. – Kornel May 17 '18 at 01:10