Search code examples
rustffi

Can a struct containing a raw pointer implement Send and be FFI safe?


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?


Solution

  • 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.