Curio and Trio Thoughts


Nathaniel Smith released the Trio library over the weekend This is a really great development. You see, when it’s just one person trying to reenvision the Python standard library, it’s easy to be viewed as some kind of lunatic. However, when there’s more than one person, then maybe, just maybe, there’s something to it. Or, maybe there’s just two lunatics. It’s hard to say.

In any case, one question that’s bound to arise is the difference between Curio and Trio. Nathaniel has written some details about this at From my vantage point, the primary difference really boils down to how the two libraries view the purpose of async/await. For Curio, it’s fundamentally about the runtime environment. If you’re in synchronous code, you use synchronous functions. If you’re interacting with asynchronous code, you use async functions. Virtually everything that happens in Curio takes place through async functions–because that’s the only mechanism the low-level runtime provides. For Trio, the separation is all about behavior. If an operation might block/reschedule/cancel, then it uses async/await. If it’s not going to block, then you use normal synchronous code.

It’s a subtle distinction, but it impacts how you view things. For example, in Curio, you might have code involving an Event like this:

evt = Event()

async def func():
    await evt.set()        # Set the event

Although setting an Event doesn’t block, you still use await because you’re working with an asynchronous Event object. That is, you’re doing something that involves the async runtime. You always use await when interacting with the runtime.

In Trio, it looks like this:

evt = Event()

async def func():
    evt.set()              # Set the event

Here, you do not use await on the event set. It doesn’t block. You never use await on things that don’t block. Of course, setting an event in Trio still involves the Trio runtime and scheduler (it might have to wake sleeping tasks elsewhere).

Both approaches have merits. For Trio, the distinction helps you pin down the exact semantics of what’s happening. For Curio, it’s all about having maximum flexibility. For example, setting an Event doesn’t normally block, but you could make a custom Event that did–maybe you’ve decided to implement some kind of alarm Event that immediately runs any tasks that are waiting for it when set–preempting even the task that just set it. By making the set() operation async, you can do this even though it muddies the waters of runtime behavior (maybe it blocks, maybe it doesn’t).

Going forward, I expect that both libraries will learn a lot from each other. Just as Curio inspired ideas in Trio, I expect ideas from Trio to inspire new developments in Curio. It’s a pretty exciting time to playing around in the world of async programming to be sure.