ZIO 1.0.0-RC18 is the last expected release candidate for ZIO 1.0. There are no breaking changes between RC18 and 1.0, but no guarantees. The current plan is to release 1.0 within a few weeks after RC18, to ensure production-worthiness of 1.0.
**Note:** *All deprecated methods will be deleted for ZIO 1.0.*
ZIO Environment
Previously, the official recommended way to use ZIO Environment was to assemble pieces using inheritance to create larger environments out of smaller environments.
In the absence of proxies, this led to some pain around construction of the environment, because creating one class from other classes is not done using value-based operators, but inheritance-specific language features that do not permit abstraction.
Moreover, this approach had several drawbacks:
- Providing just part of an environment was difficult, and was not possible to do generically (the remaining part of the environment had to be known statically).
- It was not possible to dynamically update parts of the environment inside scoped regions, which led to pain when trying to customize services in parts of the application.
Finally, the principal way to make a first service depend on a second service was to list a reference to the second service inside the first (recalling the *Cake Pattern* self-type). This led to a pattern whereby business logic would express its dependencies by using ZIO Environment, but all other parts of the application would express their dependencies using fields.
These problems have been solved in RC18 with the introduction of two new data types: Has and ZLayer. Together they have led to a new pattern of encoding environmental dependencies, which has been rolled out in ZIO Core services (Clock, Console, Random, System).
While it is still technically possible to use the older pattern, the official ZIO ecosystem (all projects in the ZIO organization on Github) will be migrated to the new pattern.
Has
The new recommended way to assemble environments involves using a new structure called *Has*. The *Has* data type has operators for building bigger environments out of smaller environments, including add, which adds a service to a *Has*, and concat, which merges two *Has* into one.
ZLayer
ZLayer is a value that represents a recipe for constructing some services in terms of other services. It is similar to a constructor in a Java or Scala application, which takes the services it depends on, and returns the service it constructs (*constructor-based dependency injection*). Unlike constructors, however, ZLayers are first-class values that compose type-safely in several ways, and they can construct many services, not just one. Additionally, they can describe the effectful and resourceful construction, such as connecting to a database, creating a database thread pool, and when the service is no longer required, freeing the thread pool, and disconnecting from the database.
ZLayer represents the most power you could ever want in describing the construction of a service. In the end, almost all applications need something with this power (as witnessed by the fact that, generally, users of previous ZIO versions resorted to building the environment using ZManaged).
ZLayers can be constructed using a variety of constructors in the companion object of ZLayer. They can be composed horizontally (when one service depends on another), and vertically (for two independent services). Services that are repeated in the graph are automatically shared, to avoid duplicate construction and release. Further, services that can be constructed in parallel are automatically constructed in parallel, to optimize service construction time.
The general pattern of ZIO Environment is now as follows:
scala
type UserRepo = Has[UserRepo.Service]
object UserRepo {
trait Service { def getUserById(id: Id): Task[User] }
def getUserById(id: Id): RIO[UserRepo, User] = ZIO.accessM(_.get.getUserById(id))
}
Then typically, a `live` value is placed in the companion object of the service, which uses ZLayer to construct a production version of the service in terms of its dependencies:
scala
val live: ZLayer[Database, Nothing, UserRepo] = ???
Services may be provided to effects that need them using the ZIOprovideLayer method, e.g. `myEffect.provideLayer(UserRepo.live)`.
ZIO Runtime System
Weak automatic supervision
Supervision is now backed by weak sets, which means that child fibers may be freely garbage collected when they cannot resume. This should address memory leaks in scenarios where large numbers of non-terminating fibers are created.
Structured concurrency
All child fibers are automatically bound to their parent fiber. When the parent fiber exits, the child fiber will be interrupted or disowned, as determined by the `SuperviseMode` it is forked with. The default supervision mode for forked fibers is interruption, which means that by default, when a parent fiber exits, its child fibers will be interrupted. This ensures fibers do not leak.
In order to recapture the previous behavior, one may use `forkDaemon`, or `disown`, which explicitly disowns a child fiber and moves it to a root set of fibers.
Safer race
Race no longer forces either side to be interruptible. Users may have to perform `left.interruptible.race(right.interruptible)` to acquire the old behavior (which punched holes in uninterruptible regions); or possibly, `left.disconnect.race(right.disconnect)` (if they want the effects to be keep running when interrupted, but in the background).
New combinators
Several new combinators are added:
`disconnect` — disconnects interruption of the effect from its parent. This allows "early interruption" without waiting for finalization. It replaces the need for all `xyzFork` variants, which are now deprecated. It's quite useful with `race`, with or without `interruptible`.
`disown` — Called by a parent fiber to disown a child fiber. Relocates the child to the root set.
New ZIO.never
When `ZIO.never` is forked, it will be garbage collected, so `ZIO.never.onInterrupt(putStrLn("Bye"))` will never execute the finalizer. This can be inconvenient for testing, so `ZIO.infinity` is added which is like `never`, but won't be garbage collected.
Deletion of Daemon Mode
Daemon mode has been deleted because in practice, code never knows whether grandchildren should be daemons, only whether immediate children should be daemons; and generally, daemon mode is a one-off for a specific forked fiber.
In place of daemon mode, `effect.forkDaemon` can be used, which is the composition of two other operators: ordinary `fork`, followed by an immediate `disown` of the child fiber.
Deprecation of *Fork Variations
All `xyzFork` variants are deprecated or removed. For example, `bracketFork`. This is thanks to the new `disconnect` operator which allows for a much more compositional approach to solving this problem. Now if you don't want interruption of something to wait around for completion, just use `effect.disconnect`.
Performance enhancements for blocking effects
Blocking effects has seen several enhancement regarding performance, with potential change in semantic:
- Now, calling `lock` on a fiber first checks to see if the fiber is already on the correct Executor and does nothing if in that case;
- `effectBlocking` doesn’t interrupt the underlying thread anymore in case of fiber interruption. If you want to revert to previous behavior, use `effectBlockingInterrupt`.
- `Blocking` executor ergonomics were updated to enhance thread reuse and limit risk to crash the server in case of massive thread leak.
ZIO General API
Laziness
Effect constructors are now lazy. This is done to ensure that if users embed side-effects in unexpected places, then errors are managed by ZIO.
Deprecated traverse/sequence
Rather than have duplicate names for operators, the decision was made to deprecate the "classic names" for traverse/sequence, in favor of friendlier and more descriptive names foreach/collectAll.
ZIO Test
Inheritance Based Specs
Spes are now defined by inheriting from `DefaultRunnableSpec` instead of using constructor arguments:
scala
object ExampleSpec extends DefaultRunnableSpec {
def spec = suite(“ExampleSpec)(
test(“addition works”) {
assert(1 + 1)(equalTo(2))
}
)
}
This provides a more natural syntax for writing tests and allows defining data or helper methods in the same object instead of requiring a separate utility object.
Type Safe Equality
To provide increased type safety, the syntax for assertions has changed from `assert(1 + 1, equalTo(2))` to `assert(1 + 1)(equalTo(2))`. This curried syntax allows the test framework to issue a compilation error if an equality assertion is comparing two unrelated types, catching bugs earlier. A ScalaFix migration rule is provided to automatically rewrite assertions to the new curried syntax.
Improved Test Console Behavior
To facilitate debugging, by default the `TestConsole` will now render output to the standard output in addition to writing it to the output buffer. This feature is configurable on a scoped basis using the `debug` and `silent` methods on `TestConsole` or the corresponding test aspects.
Test Annotations
Test annotations provide flexible information for reporting metadata about tests. ZIO Test ships with a variety of test annotations that will automatically track metrics such as the number of ignored tests, the number of times a test was retried using `flaky` and the number of times a test was repeated using `nonFlaky`. Users can apply additional annotations using test aspects, for example timing the execution of tests and showing the slowest tests using `timed` or labeling tests with `tag`.
In the future test annotations will be the basis for additional structured test reporting to support rich analysis of test results both across suites and over time.
Spy Mocking Functionality
The `mock` package contains new functionality for spy mocks in the `Spyable` trait. Spies have the ability to wrap an existing service and capture all method calls to the service being spied on as well as inputs and outputs, making it easy to verify the behavior of elements of complex systems. See the Scaladoc for additional information on this new feature.
Providing The Environment
The functionality provided by layers discussed above makes it much for users to provide tests with additional environmental requirements. In particular, the `provideCustomLayer` and `provideCustomLayerShared` methods make it extremely easy to add an additional dependency to the `TestEnvironment`, which previously required significant boilerplate.
New Aspects
A variety of new test aspects have been added to provide more flexibility than ever in modifying test execution:
- `debug` — Renders `TestConsole` output to standard output for the scope of a test.
- `diagnose` — Runs a test on its own fiber and performs a fiber dump of the test fiber and all of its children if the test does not complete within the specified time. This can be extremely useful for diagnosing concurrency bugs.
- `forked` — Runs each test on its own fiber.
- `noDelay` — Automatically runs all effects involving the `TestClock` without it needing to be manually adjusted.
- `nonTermination` — Tests that an effect does not terminate within a specified time.
- `nondeterministic` — Runs each test with a random seed generated from the system time.
- `setSeed` — Sets the random seed for a test. This is useful to deterministically "replay" tests.
- `silent` — Prevents `TestConsole` output from being rendered to standard output for the scope of a test.
- `tag` — labels tests with the specified tag. Tests can be filtered based on tags and tags will be displayed along with test output.
- `verify` — Verifies that a postcondition holds after each test.
Additional Generator Combinators
A variety of additional combinators have been provided for composing generators. In particular, the `zip` variants allow composing multiple generators in parallel when the generators do not depend on each other. This allows for more efficient shrinking and can be particularly good for large case classes or similar data structure. Additional generators are also provided for types such as `java.util.UUID`.
**JUnit integration***
A custom JUnit runner is provided for running ZIO Test specs under other build tools (like Maven, Gradle, Bazel, etc.) and under IDEs.
To get the runner, add the equivalent of following dependency definition under your build tool:
scala
libraryDependencies ++= Seq(
"dev.zio" %% "zio-test-junit" % zioVersion % "test"
)
To make your spec appear as a JUnit test to build tools and IDEs, convert it to a class and annotate it with `RunWith(classOf[zio.test.junit.ZTestJUnitRunner])` or simply extend `zio.test.junit.JUnitRunnableSpec`.
Automatic Derivation of Generators Powered By Magnolia
Automatic derivation of generators for case classes and sealed traits is now available powered by Magnolia. To get it, add the following to your dependencies:
scala
libraryDependencies ++= Seq(
“dev.zio” %% “zio-test-magnolia” %% zioVersion % “test
)
You can then automatically derive generators for your own data types:
scala
final case class Person(name: String, age: Int)
val genPerson: Gen[Random with Sized, Person] = DeriveGen[Person]
sealed trait Color
case object Red extends Color
case object Green extends Color
case object Blue extends Color
val genColor: Gen[Random with Sized, Color] = DeriveGen[Color]
Experimental Polymorphic Generators
ZIO Test now includes experimental support for polymorphic generators. These generators do not require the user to specify a particular type but instead generate random data from a variety of types that satisfy specified constraints, such as having an `Ordering`. This can be useful for testing highly polymorphic code in a generic way. See the examples in the `zio.test.poly` package and please provide feedback on this feature.
ZIO Streams
ZIO Streams includes a number of new combinators, which can be explored through the Scaladoc, but no significant breaking changes were introduced.
ZIO STM
ZIO STM has undergone a large amount of development work, adding more power, more safety, and more structures.
More Methods
STM now features many more methods, which mirror those on ZIO structures; and STM structures like TArray now have many more useful methods on them.
Stack Safety
STM is now stack safe and can handle transactions of any size without stack overflows. This is achieved not with trampolining but by using exceptions for control-flow. There is a performance hit but it is minimal for the added stack safety.
Environment
STM itself has been generalized to ZSTM, permitting use of ZIO Environment. This is useful to embed STM structures directly into the environment, which allows transactions to operate against application-wide data structures, such as caches, counters, and the like.
New Structures
- `TMap`
- `TQueue`
- `TReentrantLock`
Contributors
There were a total of 67 contributors to RC18.
Thank you to Adam Fraser, Ajay Chandran, Alex Savin, Alexander van Olst, Bojan Babić, Bojan Blagojević, Boris V.Kuznetsov, Chris Andre, dariusrobson, Dejan Mijić, Dmitry Karlinsky, Dragutin Marjanović, Evgeny Veretennikov, Fabio Serragnoli, felher, Ferdinand Svehla, Greg Holland, Ievgen Garkusha, Igal Tabachnik, ioleo, Isaias Bartelborth, Itamar Ravid, Jan Toebes, Jens Hoffmann, John A. De Goes, Jonathan Winandy, Kai, Koleman Nix, Laurynas Lubys, Luis Miguel Mejía Suárez, Marek Kadek, Mateusz Sokół‚, Matthias Langer, Maxim Davydov, Maxim Schuwalow, montrivo, Nadav Samet, Oleksandra Holubitska, Oliver Wickham, Pascal Mengelt, Pavel Shirshov, Pavels Sisojevs, Paweł Kiersznowski, peterlopen, Philippe Derome, Pierangelo Cecchetto, Pierre Ricadat, Rafael Saraiva Figueiredo, Regis Kuckaertz, reibitto, Richard Whaling, Roberto Leibman, Salar Rahmanian, Sergey Rublev, simpadjo, sken, svroonland, TapanVaishnav, Tibor Erdesz, Unclebob, Vasil Vasilev, Vasiliy Levykin, Vitalii, Wiem Zine El Abidine, wongelz, and Zachary Albia for your contributions to ZIO.
It is our incredible community of users and contributors that make ZIO possible.