bracketCase

inline suspend fun <A, B> bracketCase(crossinline acquire: suspend () -> A, use: suspend (A) -> B, crossinline release: suspend (A, ExitCase) -> Unit): B(source)

A way to safely acquire a resource and release in the face of errors and cancellation. It uses ExitCase to distinguish between different exit cases when releasing the acquired resource.

bracketCase exists out of three stages:

  1. acquisition

  2. consumption

  3. releasing

  4. Resource acquisition is NON CANCELLABLE. If resource acquisition fails, meaning no resource was actually successfully acquired then we short-circuit the effect. As the resource was not acquired, it is not possible to use or release it. If it is successful we pass the result to stage 2 use.

  5. Resource consumption is like any other suspend effect. The key difference here is that it's wired in such a way that release will always be called either on ExitCase.Cancelled, ExitCase.Failure or ExitCase.Completed. If it failed, then the resulting suspend from bracketCase will be the error; otherwise the result of use will be returned.

  6. Resource releasing is NON CANCELLABLE, otherwise it could result in leaks. In the case it throws an exception, the resulting suspend will be either such error, or a composed error if one occurred in the use stage.

Parameters

acquire

is the action to acquire the resource.

use

is the action to consume the resource and produce a result. Once the resulting suspend program terminates, either successfully, error or disposed, the release function will run to clean up the resources.

release

is the action to release the allocated resource after use terminates.

import arrow.fx.coroutines.*

class File(url: String) {
fun open(): File = this
fun close(): Unit {}
}

suspend fun File.content(): String =
"This file contains some interesting content!"
suspend fun openFile(uri: String): File = File(uri).open()
suspend fun closeFile(file: File): Unit = file.close()

suspend fun main(): Unit {
//sampleStart
val res = bracketCase(
acquire = { openFile("data.json") },
use = { file -> file.content() },
release = { file, exitCase ->
when (exitCase) {
is ExitCase.Completed -> println("File closed with $exitCase")
is ExitCase.Cancelled -> println("Program cancelled with $exitCase")
is ExitCase.Failure -> println("Program failed with $exitCase")
}
closeFile(file)
}
)
//sampleEnd
println(res)
}