# Zio (Scala library)

**URL:** <https://trio.discourse.group/t/zio-scala-library/72>\
**Category:** Structured concurrency\
**Created:** [February 11, 2019, 12:24am UTC](https://trio.discourse.group/t/zio-scala-library/72 "2019-02-11T00:24:40Z")\
**Posts on this page:** 1\
**Showing post:** 3

<div class="post-metadata">

**Author:** ![njs](https://yyz2.discourse-cdn.com/free1/user_avatar/trio.discourse.group/njs/32/11_2.png) [@njs](https://trio.discourse.group/u/njs)\
**Post date:** [February 13, 2019, 4:59am UTC](https://trio.discourse.group/t/zio-scala-library/72/3 "2019-02-13T04:59:07Z")

</div>

> [@anandabits](#):
>
> Those who are interested can find an overview of Zio’s design here: [https://m.youtube.com/watch?v=wi\_vLNULh9Y](https://m.youtube.com/watch?v=wi_vLNULh9Y)

Thanks for the link! I was vaguely aware of Zio but had not found the right reference to figure out how the big ideas fit together. Impressions from watching the video:

Well… there’s a tremendous amount of machinery used for static typing and the pure functional monad part (you don’t write a program, you write a program that returns a program, etc.). Of course this way of approaching things has benefits; I’m just not sure I followed all of it :-).

But if you squint past that, I think the core concurrency primitives are:

- A very traditional `go`-like operator (they call it `fork`) that returns a “fiber handle”, and then you can use this handle object to `join` or `interrupt` the “fiber”. Conceptually I think the only way this really differs from a JS `Promise` is that it supports cancellation, and it’s pretty much isomorphic to a Twisted `Deferred` or asyncio `Future` (which do support cancellation), or even to a POSIX thread if we pretend POSIX thread cancellation is usable.

- And there are a bunch of nice higher-level tools built on top of the bare “fiber” concept, that carefully manage the fiber handles and interrupt things appropriately. For example `race` is implemented using the public fiber APIs, and manually keeps track of which fibers are running, handles errors from them, cancels the loser, etc. Having done all that, they end up with something whose semantics looked pretty safe and sensible to me. The talk makes a big deal about most other implementations of `race` not getting these details right, which sounds fair.

- And then you have `supervise`, which lets you manually demarcate a chunk of code, and when you get to the end of that code, if there were any fibers that it created but then leaked, those fibers are automatically cancelled.

So… this is a really interesting example to me. They seem to be worrying about the right things, and I get the feeling that you can indeed use this system to effectively solve lots of real problems. But… the one thing they’re missing is exactly the stuff from the “go statement considered harmful” article. And if they had that, I think it would make their design substantially simpler and better behaved at the same.

There are two key differences between this approach and how Trio does it:

- We reify an object to represent each call to `supervise`
- Then we make this reified object a mandatory argument to `fork`

This seems like a pretty small change, but it does a lot!

It eliminates fiber leaks by making them inexpressible. Users can’t forget to call `supervise`.

It’s great that they were very clever and careful about implementing higher-level operations like `race`, but with my version, you don’t have to be clever and careful, because the type system _forces_ you to handle the other fibers.

Since you can’t leak fibers, you get better local reasoning about code – if I invoke some kind of subroutine, and I don’t pass in a `supervise` object, then the type system guarantees that any fibers that the call spawns internally cannot outlive the call. I’ve previously called this property “respecting causality”, and [it makes many common errors inexpressible](https://trio.discourse.group/t/discussion-some-thoughts-on-asynchronous-api-design-in-a-post-async-await-world/32/4). With their current design, you could get this by wrapping every subroutine invocation in a `supervise`, but who wants to do that?

And in terms of design complexity, you get all these benefits essentially “for free” – it’s just a very simple tweak to stuff they needed anyway, no new concepts, no rocket science. It’s so simple that in this respect, Trio actually manages gets stronger guarantees out of Python’s type system than ZIO gets out of Scala’s.

When people start learning Trio, this is a very common question: “how do I get a reference to the enclosing nursery?” They expect there to be some equivalent to ZIO’s `fork`, that lets then put a task into the enclosing `supervise`, where-ever it may be. So this is why we don’t have that operation 🙂

On the other hand, an interesting thing about the ZIO style is that it’s probably easier to retrofit into existing libraries/ecosystems. (I think Kotlin works this way too?) I wonder if it would have applications in, for example, Golang.

* * *

Two more minor thoughts:

When their `supervise` block exits, they cancel all the nested fibers, similar to libdill. When Trio exits a nursery block, it stops and waits for all the nested tasks to complete. One trade-off is that this forces ZIO/libdill to have fiber handles, and a `join` operation to wait for a fiber to finish. Trio is able to get away with skipping both of those concepts. I can see how ZIO’s approach makes sense for them, since the emphasis on pure-functional-style means they really want to join fibers to find out what they evaluated too, and the emphasis on high-level combinators like `race` and `par` hides the tedium of `join`ing from most users. Trio makes side-effects a more first-class citizen, so we use those as our primitive for getting results out of tasks, instead of `join`. Oh huh, and it looks like more recently ZIO [added the Trio style as an option, too](https://github.com/scalaz/scalaz-zio/issues/124).

Error handling: ZIO’s way of handling and propagating errors is sufficiently foreign to me that I don’t really know how to compare it to Trio! I worry that their tracebacks might not be very useful? And I noticed that they implement the “crash handler” pattern, where you register some callback to handle errors from unjoined fibers. (Or all crashed fibers?) This pattern always makes me wonder how the crash handler preserves enough context to know how to handle an error. But I am curious how it all fits together.

---

_[View the full topic](https://trio.discourse.group/t/zio-scala-library/72)._
