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?
Rc uses non-atomic reference counting. This means that overhead is very low, but an Rc cannot be sent between threads, and consequently Rc does not implement Send . As a result, the Rust compiler will check at compile time that you are not sending Rc s between threads.
Rust captures this through the Send and Sync traits. A type is Send if it is safe to send it to another thread. A type is Sync if it is safe to share between threads (T is Sync if and only if &T is Send).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With