11

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?

5
  • 4
    Why not just implement Send? Commented May 9, 2018 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? Commented May 9, 2018 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? Commented May 10, 2018 at 9: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. Commented May 10, 2018 at 9:18
  • I meant unsafe impl Send for Storage {};. Here's a link to the playground where that code does indeed compile: play.rust-lang.org/… Commented May 10, 2018 at 9:59

1 Answer 1

15

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.

Sign up to request clarification or add additional context in comments.

2 Comments

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.