Resource

Effectfully allocates and releases a resource. Forms a MonadError on the resource type when the effect type has a Bracket instance.

The Acquiring and releasing Resources section of the tutorial provides some additional context and examples regarding Resource.

import cats.effect.BracketThrow

abstract class Resource[F[_], A] {
  def use[B](f: A => F[B])(implicit F: BracketThrow[F]): F[B]
}

Nested resources are released in reverse order of acquisition. Outer resources are released even if an inner acquisition, use or release fails.

You can lift any F[A] with an Applicative instance into a Resource[F, A] with a no-op release via Resource.liftF:

import cats.effect.{IO, Resource}

val greet: String => IO[Unit] = x => IO(println("Hello " + x))
// greet: String => IO[Unit] = <function1>

Resource.liftF(IO.pure("World")).use(greet).unsafeRunSync()
// Hello World

Moreover it’s possible to apply further effects to the wrapped resource without leaving the Resource context via evalMap:

import cats.effect.{IO, Resource}

val acquire: IO[String] = IO(println("Acquire cats...")) *> IO("cats")
// acquire: IO[String] = Bind(
//   source = Delay(thunk = <function0>),
//   f = cats.effect.IO$$Lambda$11685/0x000000080311f040@350bcb18,
//   trace = StackTrace(
//     stackTrace = List(
//       cats.effect.internals.IOTracing$.buildFrame(IOTracing.scala:48),
//       cats.effect.internals.IOTracing$.buildCachedFrame(IOTracing.scala:39),
//       cats.effect.internals.IOTracing$.cached(IOTracing.scala:34),
//       cats.effect.IO.flatMap(IO.scala:133),
//       cats.effect.IO.$times$greater(IO.scala:770),
//       repl.MdocSession$App7.<init>(io.md:203),
//       repl.MdocSession$App6.<init>(io.md:177),
//       repl.MdocSession$App5.<init>(io.md:155),
//       repl.MdocSession$App.<init>(io.md:137),
//       repl.MdocSession$.app(io.md:3),
//       mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$2(DocumentBuilder.scala:89),
//       scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
//       scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
//       scala.Console$.withErr(Console.scala:193),
//       mdoc.internal.document.DocumentBuilder$$doc$.$anonfun$build$1(DocumentBuilder.scala:89),
//       scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18),
//       scala.util.DynamicVariable.withValue(DynamicVariable.scala:59),
//       scala.Console$.withOut(Console.scala:164),
//       mdoc.internal.document.DocumentBuilder$$doc$.build(DocumentBuilder.scala:88),
//       mdoc.internal.markdown.MarkdownBuilder$.buildDocument(MarkdownBuilder.scala:44),
//       mdoc.internal.markdown.Processor.processScalaInputs(Processor.scala:185),
//       mdoc.internal.markdown.Processor.processScalaInputs(Processor.scala:152),
//       mdoc.internal.markdown.Processor.processDocument(Processor.scala:52),
//       mdoc.internal.markdown.Markdown$.toMarkdown(Markdown.scala:131),
//       mdoc.internal.cli.MainOps.handleMarkdown(MainOps.scala:82),
//       mdoc.internal.cli.MainOps.handleFile(MainOps.scala:110),
//       mdoc.internal.cli.MainOps.$anonfun$generateCompleteSite$1(MainOps.scala:156),
//       scala.collection.LinearSeqOps.foldLeft(LinearSeq.scala:168),
//       scala.collection.LinearSeqOps.foldLeft$(LinearSeq.scala:164),
//       scala.collection.immutable.List.foldLeft(List.scala:79),
//       mdoc.internal.cli.MainOps.generateCompleteSite(MainOps.scala:155),
//       mdoc.internal.cli.MainOps.run(MainOps.scala:177),
//       mdoc.internal.cli.MainOps$.process(MainOps.scala:269),
//       mdoc.Main$.process(Main.scala:26),
//       mdoc.Main$.process(Main.scala:21),
//       mdoc.Main$.main(Main.scala:16),
//       mdoc.Main.main(Main.scala),
//       java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method),
// ...
val release: String => IO[Unit] = _ => IO(println("...release everything"))
// release: String => IO[Unit] = <function1>
val addDogs: String => IO[String] = x =>
  IO(println("...more animals...")) *> IO.pure(x + " and dogs")
// addDogs: String => IO[String] = <function1>
val report: String => IO[String] = x =>
  IO(println("...produce weather report...")) *> IO("It's raining " + x)
// report: String => IO[String] = <function1>

Resource.make(acquire)(release).evalMap(addDogs).use(report).unsafeRunSync()
// Acquire cats...
// ...more animals...
// ...produce weather report...
// ...release everything
// res3: String = "It's raining cats and dogs"

Example

import cats.effect.{IO, Resource}

def mkResource(s: String) = {
  val acquire = IO(println(s"Acquiring $s")) *> IO.pure(s)

  def release(s: String) = IO(println(s"Releasing $s"))

  Resource.make(acquire)(release)
}

val r = for {
  outer <- mkResource("outer")
  inner <- mkResource("inner")
} yield (outer, inner)

r.use { case (a, b) => IO(println(s"Using $a and $b")) }.unsafeRunSync()

If using an AutoCloseable create a resource without the need to specify how to close.

Examples

With scala.io.Source

import cats.effect._

val acquire = IO {
  scala.io.Source.fromString("Hello world")
}

Resource.fromAutoCloseable(acquire).use(source => IO(println(source.mkString))).unsafeRunSync()

With java.io using IO

import java.io._
import scala.jdk.CollectionConverters._
import cats.effect._

def readAllLines(bufferedReader: BufferedReader, blocker: Blocker)(implicit cs: ContextShift[IO]): IO[List[String]] =
  blocker.delay[IO, List[String]] {
    bufferedReader.lines().iterator().asScala.toList
  }

def reader(file: File, blocker: Blocker)(implicit cs: ContextShift[IO]): Resource[IO, BufferedReader] =
  Resource.fromAutoCloseableBlocking(blocker)(IO {
      new BufferedReader(new FileReader(file))
    }
  )

def readLinesFromFile(file: File, blocker: Blocker)(implicit cs: ContextShift[IO]): IO[List[String]] = {
  reader(file, blocker).use(br => readAllLines(br, blocker))
}

A java.io example agnostic of the effect type

import java.io._
import cats.effect._
import cats.syntax.flatMap._

def reader[F[_]](file: File, blocker: Blocker)(implicit F: Sync[F], cs: ContextShift[F]): Resource[F, BufferedReader] =
  Resource.fromAutoCloseableBlocking(blocker)(F.delay {
    new BufferedReader(new FileReader(file))
  })

def dumpResource[F[_]](res: Resource[F, BufferedReader], blocker: Blocker)(implicit F: Sync[F], cs: ContextShift[F]): F[Unit] = {
  def loop(in: BufferedReader): F[Unit] =
    F.suspend {
      blocker.delay(in.readLine()).flatMap { line =>
        if (line != null) {
          System.out.println(line)
          loop(in)
        } else {
          F.unit
        }
      }
    }
  res.use(loop)
}

def dumpFile[F[_]](file: File, blocker: Blocker)(implicit F: Sync[F], cs: ContextShift[F]): F[Unit] =
  dumpResource(reader(file, blocker), blocker)