Hi, everyone
I am trying to do a ffi binding, and all the functions imported from c should only be called in current thread. So I want a compiler error in the following code sample when the functions come to another thread. Is it possible?
Thanks.
pub struct MyFFI {
call: extern "C" fn() -> i32,
}
static mut ffi:Option<MyFFI> = None;
#[no_mangle]
pub extern "C" fn init(b:MyFFI) {
unsafe {
ffi = Some(b);
}
}
pub fn call() ->i32 {
(get_ffi().call)()
}
pub fn get_ffi() ->&'static MyFFI {
unsafe {
ffi.as_ref().unwrap()
}
}
pub fn test() {
std::thread::spawn(||{
call();
});
}
(Playground)
You can prevent instances from crossing a thread boundary by ensuring they're !Send. To ensure only one thread can call FFI functions, you can do something like this:
use std::marker::PhantomData;
use std::sync::Once;
#[derive(Copy,Clone)]
pub struct MyFFI {
call_fn: extern "C" fn() -> i32,
phantom: PhantomData<*mut ()>, // !Send + !Sync
}
impl MyFFI {
pub fn get()->Option<Self> {
static INIT: Once = Once::new();
thread_local! {
static FFI: Option<MyFFI> = {
let mut ffi = None;
INIT.call_once(|| ffi = Some(MyFFI::init()));
ffi
}
}
FFI.with(|&ffi| ffi)
}
fn init()->Self { todo!() }
pub fn call(&self)->i32 {
(self.call_fn)()
}
}
pub fn test() {
let ffi = MyFFI::get().unwrap();
std::thread::spawn(||{
// Compile error here: MyFFI is !Send
ffi.call();
});
}
Playground
(NB: MyFFI::get() will return None if called in a second thread)
use std::marker::PhantomData;
use std::sync::Once;
#[derive(Copy,Clone)]
pub struct MyFFI {
call_fn: extern "C" fn() -> i32,
phantom: PhantomData<*mut ()>, // !Send + !Sync
}
impl MyFFI {
pub fn get()->Option<Self> {
static INIT: Once = Once::new();
thread_local! {
static FFI: Option<MyFFI> = {
let mut ffi = None;
INIT.call_once(|| ffi = Some(MyFFI::init()));
ffi
}
}
FFI.with(|&ffi| ffi)
}
fn init()->Self { todo!() }
pub fn call(&self)->i32 {
(self.call_fn)()
}
}
pub fn test() {
std::thread::spawn(||{
let ffi = MyFFI::get().unwrap();
ffi.call();
});
}
I don't quite understand the solution. I tried comment phantom field, and compiler complained about lifttime. So I move ffi from outside into thread closure, and it's ok now whether phantom field exists or not.
The PhantomData prevents any MyFFI object from crossing a thread boundary, and MyFFI::get() ensures that only one MyFFI instance is ever created. In your example, you're creating that instance on the spawned thread instead of the main one, which is still a form of single-threaded operation.
That's clear. Thanks for all the replies.