Need a second opinion on "async Rust" <-> "C library with callbacks" interop
⚓ Rust 📅 2025-06-24 👤 surdeus 👁️ 16Hello, wise people of Rust!
What's the problem
I have a convoluted use case of async Rust co-operating with a C library with callbacks.
The regular flow of the library, as used from C
- C code calls library (say
setup_device()). - The library does its magic, but to perform some actions calls callbacks (say, a bunch of
read_register(),write_register()events). - Callbacks perform some network
send()/recv()(yep, I know, read register via network. Nobody claims people at Microchip are sane). - Far side performs an action and responds (or a packet is lost)
- Upon response or timeout callback returns outcome to the library.
- Library performs many more turnarounds and finally passes the final result to the initial caller.
What I have:
- C library is operational (part of another project)
- It properly links to Rust code (thanks to
cccrate) - I can call the necessary functions (a hefty
unsafe extern "C" { ... }) block. - Callbacks are handled by stubs in Rust (they now just log args and return failure), a few
#[unsafe(no_mangle)] pub extern "C" fn XXX(){ ... }
- I have a reactor loop and all the necessary network code.
My idea
Now it is time to stitch it together.
- Have main reactor with
loop { select! { ... }}. - Have a separate blocking task
async fn lib_requestor_loop(){}-
connected with main reactor via async mpsc queue
lib_queueofenum Cmd{}, -
each
Cmdhas a one-shot channellib_replyfor reply from the library. -
Dispatched
Cmdcalls blocking the library-
lib callbacks issue
enum NetReq{}via async mpsc queuenet_rqueststo the main reactor (try_sendis sync, no problem), eachNetReqhas a field with a sync channelnet_replyforenum NetResult{}-s (i don't see a sync one-shot implementation anywhere, Tokio's syncone_shot::try_recv()doesn't block). Callback blocks on recv onnet_reply.- The main reactor processes the request and reports success/failure back via
net_reply.
- The main reactor processes the request and reports success/failure back via
-
Recv on
net_replyunblocks, callback passes the result back to library -
Blocking call to the library returns
-
-
The result is sent back via
lib_replychannel to the main reactor
-
- Profit!
Do you have any improvements or suggestions for my flow? My main concern is callback-main reactor communication, especially the slippery situation of async reactor and sync callbacks.
Thanks in advance.
1 post - 1 participant
🏷️ rust_feed