Need a second opinion on "async Rust" <-> "C library with callbacks" interop

⚓ Rust    📅 2025-06-24    👤 surdeus    👁️ 5      

surdeus

Warning

This post was published 49 days ago. The information described in this article may have changed.

Hello, 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

  1. C code calls library (say setup_device()).
  2. The library does its magic, but to perform some actions calls callbacks (say, a bunch of read_register(), write_register() events).
  3. Callbacks perform some network send()/recv() (yep, I know, read register via network. Nobody claims people at Microchip are sane).
  4. Far side performs an action and responds (or a packet is lost)
  5. Upon response or timeout callback returns outcome to the library.
  6. 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 cc crate)
  • 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_queue of enum Cmd{},

    • each Cmd has a one-shot channel lib_reply for reply from the library.

    • Dispatched Cmd calls blocking the library

      • lib callbacks issue enum NetReq{} via async mpsc queue net_rquests to the main reactor (try_send is sync, no problem), each NetReq has a field with a sync channel net_reply for enum NetResult{}-s (i don't see a sync one-shot implementation anywhere, Tokio's sync one_shot::try_recv() doesn't block). Callback blocks on recv on net_reply.

        • The main reactor processes the request and reports success/failure back via net_reply.
      • Recv on net_reply unblocks, callback passes the result back to library

      • Blocking call to the library returns

    • The result is sent back via lib_reply channel 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

Read full topic

🏷️ rust_feed