2/15/2025, 11:14:00 AM

The roots of Send + Sync run deep

A couple of months ago, I started on an ambitious project to build a full stack web framework that would offer both superior performance of rust based frameworks and the first class developer ergonomics of the more dynamic language oriented frameworks in wide use today.

A core part of the effort was to use the monoio library with its thread per core and non-work stealing architecture to enable good ergonomics without the unnecessary constraints of the dreaded Send + Sync architecture of tokio.

The progress has been good so far. I have the Controller part of MVC working. To paraphrase the Hooli dudebros: Routing is optimal. Receiving requests from curl is optimal. Sending responses back to curl is optimal. Middleware is optimal.

Another Roadblock?

With this quick win under my belt, I decided to move onto the next part of MVC, the Model. And this is where it swerved straight into a tokio sized Send + Sync roadblock. You see, the Database interactions in an app are highly io-bound where you are waiting for the DB to send you the results for your query. So you naturally want them to be async, allowing you to move forward on other requests while the DB did its magic. So this meant sync drivers like diesel were out.

On the async side, everyone pointed to a library called sqlx. On the surface this library looked very promising -

  • Support for the trifecta of Mysql, Postgres and Sqlite? Check.
  • Compile-time checking of sql queries? Huh, interesting.
  • Query Builders and ORMs like SeaOrm. Check.
  • Works with monoio? Oh, no.

Okay, this might be because it’s also a high level library, right? No matter, we’ll get the raw database drivers and whip up some quick query builders on top. A quick google search for rust postgres shows a library that might it? Aaaaand its also dependent on runtimes and has a package called tokio-postgres.

sigh Yep, turns out that in the world of Async Rust, everything has to be written against specific runtimes. Including database drivers. The signs were there early; I should have expected this.

So now what (again)

The dominance of tokio and its imposition of Send + Sync across the entire ecosystem even at database driver levels is truly something. I can understand why the discussions around Async Rust get this heated.

With that, I have five options right now -

  1. Try to patch sqlx and SeaOrm to also support monoio?
  2. Check if there are modular parts in the drivers and reuse them to build my own Query Builders and ORMs?
  3. Write my own database drivers and ORMs on top of it?
  4. Accept defeat and go back to tokio and accept the curse of Send + Sync?
  5. Go back to writing PHP and Javascript?

5 is not an option because Rust is what I want to learn and write. No other language has hooked me quite this way since my first taste of Visual Basic 6. I don’t want to do 3 at all unless I really have to, and my pride won’t let me do 4.

So it’s either 1 and 2, and given the theme of this project, 2 might be the best approach to take. Working with databases at that low level is something I haven’t done yet, and it gives me an opportunity to really flex my programming muscles after a long time. And both sqlx and tokio-postgres have some modules that don’t look like they depend on a runtime, so this might actually be possible?

Stay tuned for more updates on this adventure—there’s still a lot to figure out!

And if you want to flex your programming muscles too, drop a ping and I’ll see if I can arrange some early access to the repos :)