A finalizer is an effectful cleanup action associated with an acquired resource. Resource management registers the action with an open scope, then runs it when that scope closes. This makes cleanup part of the streaming computation rather than an afterthought. section 15.3.3
Scopes form a tree. Closing a scope first closes its subscopes and then evaluates its own finalizer. A finalizer is represented as F[Unit], so cleanup remains explicit, effectful, and composable. section 15.3.3
Resource-safe streaming must finalize resources when:
- the producer is exhausted; section 15.6
- evaluation terminates early; section 15.6
- evaluation fails with an unhandled error. section 15.6
Nested scopes preserve cleanup structure. Subscope finalizers run in reverse registration order, followed by the enclosing finalizer. Cleanup continues after an individual failure, while the first observed error is retained and reported after the remaining finalizers have run. section 15.3.3
Closing a file
Stream.resource(Task(Source.fromFile(path)))(s => Task(s.close())) acquires a Source and registers s.close() as its finalizer. A file-writing stream uses the same pattern for BufferedWriter, ensuring that the writer is closed when processing finishes, stops early, or fails. section 15.3.3 section 15.6
The public resource pattern matters more than its implementation: acquire a value, expose it as a stream, and pair it with release logic that receives the acquired value. The scope machinery supplies the operational guarantee. section 15.3.3
def resource[F[_], R](acquire: F[R])(release: R => F[Unit]): Stream[F, R]
Use the resource constructor to connect an acquisition effect with a value-dependent finalizer. section 15.3.3
