Creating a library that spawns a thread

I'm pretty new to Rust so forgive me.

I'd like to make a library using Rust, and I'd like that library to have an init() function which spawns a thread, and for subsequent function calls to send messages to that thread.

I also want this library to be accessible from C code.

My question is: what would be the best way to go about doing that?

If I was writing the library in C or C++, I'd create global variables to handle the synchronization between my library's personal thread and the calling thread. But, it appears that creating a global that's accessible across threads in Rust is an awkward process typically involving 'unsafe's in my code.

So what about returning a reference to a data structure which contains the stuff needed to send messages to the thread, and requiring subsequent calls to pass in this structure? Is that a good idea? Would it work from a C program without becoming unwieldy? If this is something reasonable, can someone give me an example of how I could write it?

For background, I'm making a library for a certain type of network server which will feed data to its clients in real time, sending constant synchronization signals. These signals need to be sent whether the main program has any new information or not. I don't want the user of my library to worry about calling a function every X milliseconds to ensure the packets are sent on time, so a separate thread seems like the answer. I want to spare the user of my library the business of creating a thread himself, as well, especially in C where it can be an uncomfortable process with portability concerns.

Maybe my head is in completely the wrong place, or I'm making some poor assumptions? I'd be interested to hear suggestions.

Usually if you want to create a global variable in Rust, which can't be const-initialized, the solution is to use the lazy_static crate. It requires the type to be Sync though, which the mpsc::Sender is not, so you'd have to wrap it also in a Mutex.

That would be a nicer solution. Usually you create a struct in Rust and let the init() return a pointer to it:

struct My { x: std::sync::mpsc::Sender<u8> }
pub extern fn init() -> *mut My {
    let my = My { x: /* initialize */ };

(the into_raw casts a Rusts owned pointer (Box) into a C raw pointer, without destroying the box). In C, you define it as:

struct My;
My *init(void);
void foo(My*, other_arguments);

All the other functions take *mut My as a parameter (you can cast it to &mut My in Rust code, to implement the function). If you have a destroy function, you can destroy the box by re-creating the box (Box::from_raw(ptr)) and just letting it go out of scope.

If that additional parameters is unwieldy, C code can store it in a global and have its own wrapper functions.

Please note that if you end up using the mpsc::Sender, the C code has to implement its own synchronization or use your struct from only one thread.

1 Like

Awesome! I think this will get me started. That latter method seems like an elegant way to go about it. Thanks.

Do you need #[repr(c)] on the struct? I guess not since only a pointer is passed through FFI, but maybe worth mentioning if any struct will be passed by value.