I'm freakin elated to share something I've been hammering away at: rzmq!
If you've been in the distributed systems or messaging game for a while, ZeroMQ (ØMQ) needs no introduction. It's the swiss-army knife of messaging, a "socket library that acts like a concurrency framework." It's powerful, flexible, and has been a workhorse for countless projects. But let's be honest, the C libzmq core, while incredibly stable, hasn't seen the kind of architectural evolution that takes full advantage of the latest and greatest in OS-level I/O and language advancements.
That's where the spark for rzmq ignited. I had this burning desire – a need for speed, if you will – to see what ZeroMQ could look like if we infused it with some modern Rust-aceans and the raw power of Linux's io_uring.
The "Why": Safety, Speed, and Sanity
So, what's the big deal?
Rust's Fearless Concurrency & Memory Safety: No more data races hiding in C FFI calls. Rust's ownership model and compiler checks give us a massive head start in building a robust, concurrent messaging library without those dreaded segfaults or memory leaks.
XTREME HIGH PERFORMANCE (The Goal!): This was a big one. libzmq is fast, no doubt. But Linux has given us io_uring, a truly asynchronous interface to the kernel that promises significantly reduced syscall overhead and incredible I/O throughput. rzmq is built with an opt-in io_uring backend from the get-go for its TCP transport. Imagine ZeroMQ patterns flying on io_uring rails – that's the dream!
TCP Corking (Linux Goodness): Another Linux trick up our sleeve! rzmq supports TCP Corking. For those sending lots of small message parts (hello, multi-part ZMQ messages!), corking can batch them up into fewer TCP segments, reducing network chatter and potentially improving latency and throughput.
Asynchronous from the Ground Up: rzmq is built on Tokio. Everything is async/await, making it a natural fit for modern Rust applications.
Under the Hood: Actors on the Loose!
Internally, rzmq uses an actor model. Think of it like this: each socket you create isn't just a passive handle; it's managed by a dedicated, lightweight actor (or a small troupe of them) that handles its state, message routing, and protocol logic. These actors chat with each other using asynchronous channels. This design helps keep things clean, concurrent, and easier to reason about (well, mostly! Async actors can still be a party).
The cool part? Even with this actor-based design, the focus remains on performance. We're not just shuffling messages between threads; we're trying to make each step as efficient as possible, especially when io_uring is in play.
A Glimpse of the API
I wanted to keep the API feel familiar to libzmq veterans while being idiomatic Rust.
// Super simplified pseudo-Rust
async fn run_my_service() -> Result<(), RzmqError> {
// 1. Create a context (the heart of rzmq)
let ctx = rzmq::Context::new()?;
// 2. Create sockets with specific patterns
let publisher = ctx.socket(SocketType::Pub)?;
let subscriber = ctx.socket(SocketType::Sub)?;
// 3. Bind or connect your sockets
publisher.bind("tcp://*:5555").await?;
subscriber.connect("tcp://localhost:5555").await?;
// 4. Set some options (just like libzmq!)
subscriber.set_option(rzmq::SUBSCRIBE, b"NASDAQ:").await?; // Topic filter
publisher.set_option(rzmq::SNDHWM, &1000.to_ne_bytes()).await?; // Send High-Water Mark
// 5. Send and receive asynchronously
// In a real app, these would be in different tasks/threads
tokio::spawn(async move {
loop {
publisher.send(Msg::from_static(b"NASDAQ:AAPL 150.00")).await?;
tokio::time::sleep(Duration::from_secs(1)).await;
}
});
loop {
let msg = subscriber.recv().await?;
println!("Got: {}", String::from_utf8_lossy(msg.data().unwrap()));
}
// 6. Graceful shutdown (important!)
// ctx.term().await?; // In a real app, handle shutdown signals
// Ok(())
}
This is just a taste, but you get the idea: Context, Socket, bind, connect, send, recv. Familiar, but async and Rust-powered.
Current Status: "Handle With Care, Excitement Welcome!"
rzmq is definitely in the experimental stage. While we have core socket types (PUB/SUB, REQ/REP, PUSH/PULL, DEALER/ROUTER) working over TCP, IPC (Unix), and Inproc, and the io_uring backend is functional, there's still a journey ahead.
Implemented & Tested: Core patterns, ZMTP 3.1 basics, many common socket options (SNDHWM, RCVHWM, LINGER, SNDTIMEO, RCVTIMEO, RECONNECT_IVL, LAST_ENDPOINT, etc.), TCP Corking, experimental Zerocopy Send (with io_uring), and a socket monitoring system. We've got a growing suite of tests and benchmarks.
Work in Progress:
Security: This is a big one. The groundwork for NULL, PLAIN, and CURVE (via libsodium-rs) is there, but CURVE authentication, in particular, needs more love and rigorous testing to be considered secure. ZAP (ZeroMQ Authentication Protocol) integration is also on the roadmap.
Full Option Parity: Not all libzmq options are implemented yet.
Performance Deep Dive: While the io_uring bits are exciting, extensive, comparative benchmarking against libzmq across all scenarios is still to come. We're also looking into advanced io_uring features like registered buffers and deeper multishot receive optimizations.
Robustness: Battle-testing in diverse, high-load, and failure-prone network environments.
What's Next? Dogfooding!
I'm planning to integrate rzmq into some of my own internal backend services. There's no better way to find the rough edges and drive improvements than by using it in real-world(ish) scenarios. This will be a crucial step in hardening the library.
Join the Fun?
If you're intrigued by the idea of a pure Rust, high-performance ZeroMQ, or if you're an io_uring enthusiast, I'd love for you to check out the RZMQ Github. Contributions, feedback, bug reports, or even just a star to show interest are all hugely appreciated!
It's been a challenging but incredibly rewarding project so far. Re-thinking ZeroMQ's patterns with Rust's safety and Tokio's async model, then layering on io_uring's potential, has been a blast. Here's to pushing the boundaries of messaging performance in Rust!
Stay tuned for more updates!