Skip to main content
Version: ZIO 2.x (WIP)

Handling Resources

This section looks at some of the common ways to safely handle resources using ZIO.

ZIO's resource management features work across synchronous, asynchronous, concurrent, and other effect types, and provide strong guarantees even in the presence of failure, interruption, or defects in the application.

Finalizing

ZIO provides similar functionality to try / finally with the ZIO#ensuring method.

Like try / finally, the ensuring operation guarantees that if an effect begins executing and then terminates (for whatever reason), then the finalizer will begin executing.

val finalizer = 
UIO.succeed(println("Finalizing!"))
// finalizer: UIO[Unit] = zio.ZIO$Succeed@56391f66

val finalized: IO[String, Unit] =
IO.fail("Failed!").ensuring(finalizer)
// finalized: IO[String, Unit] = zio.ZIO$Ensuring@8c8d970

The finalizer is not allowed to fail, which means that it must handle any errors internally.

Like try / finally, finalizers can be nested, and the failure of any inner finalizer will not affect outer finalizers. Nested finalizers will be executed in reverse order, and linearly (not in parallel).

Unlike try / finally, ensuring works across all types of effects, including asynchronous and concurrent effects.

Acquire Release

A common use for try / finally is safely acquiring and releasing resources, such as new socket connections or opened files:

val handle = openFile(name)

try {
processFile(handle)
} finally closeFile(handle)

ZIO encapsulates this common pattern with ZIO#acquireRelease, which allows you to specify an acquire effect, which acquires a resource; a release effect, which releases it; and a use effect, which uses the resource.

The release effect is guaranteed to be executed by the runtime system, even in the presence of errors or interruption.

val groupedFileData: IO[IOException, Unit] = 
openFile("data.json").acquireReleaseWith(closeFile(_)) { file =>
for {
data <- decodeData(file)
grouped <- groupData(data)
} yield grouped
}

Like ensuring, acquire releases have compositional semantics, so if one acquire release is nested inside another acquire release, and the outer resource is acquired, then the outer release will always be called, even if, for example, the inner release fails.

Next Steps

If you are comfortable with resource handling, then the next step is to learn about basic concurrency.