22

Is there a better way to ensure resources are properly released - a better way to write the following code ?

        val out: Option[FileOutputStream] = try {
          Option(new FileOutputStream(path))
        } catch {
          case _ => None
        }


        if (out.isDefined) {

          try {
            Iterator.continually(in.read).takeWhile(-1 != _).foreach(out.get.write)
          } catch {
            case e => println(e.getMessage)
          } finally {
            in.close
            out.get.flush()
            out.get.close()
          }

        }
Dzhu
  • 4,311
  • 5
  • 36
  • 48
  • Because I need to be able to nest multiple java.lang.AutoCloseable instances, each of which depends upon the prior one successfully instantiating, I finally hit upon a pattern that has been very useful for me. I wrote it up as an answer on similar StackOverflow question: http://stackoverflow.com/a/34277491/501113 – chaotic3quilibrium Jan 22 '16 at 19:59

3 Answers3

21

Something like that is a good idea, but I'd make it a method:

def cleanly[A,B](resource: => A)(cleanup: A => Unit)(code: A => B): Option[B] = {
  try {
    val r = resource
    try { Some(code(r)) }
    finally { cleanup(r) }
  } catch {
    case e: Exception => None
  }
}

(note that we only catch once; if you really want a message printed in one case and not the other, then you do have to catch both like you did). (Also note that I only catch exceptions; catching Error also is usually unwise, since it's almost impossible to recover from.) The method is used like so:

cleanly(new FileOutputStream(path))(_.close){ fos =>
  Iterator.continually(in.read).takeWhile(_ != -1).foreach(fos.write)
}

Since it returns a value, you'll get a Some(()) if it succeeded here (which you can ignore).


Edit: to make it more general, I'd really have it return an Either instead, so you get the exception. Like so:

def cleanly[A,B](resource: => A)(cleanup: A => Unit)(code: A => B): Either[Exception,B] = {
  try {
    val r = resource
    try { Right(code(r)) } finally { cleanup(r) }
  }
  catch { case e: Exception => Left(e) }
}

Now if you get a Right, all went okay. If you get a Left, you can pick out your exception. If you don't care about the exception, you can use .right.toOption to map it into an option, or just use .right.map or whatever to operate on the correct result only if it is there (just like with Option). (Pattern matching is a useful way to deal with Eithers.)

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • Why do you assign the resource to r? Why not use it directly. Do you see anything wrong with this simplification, removing one try. `def cleanly[A <: java.io.Closeable, B](resource: A)(code: A => B): Either[Exception, B] = { try { Right(code(resource)) } catch { case e:Exception => Left(e) } finally { resource.close } }` – rvange Dec 02 '13 at 23:03
  • 3
    @rvange - Generating the resource may cause an exception, so you want to call by name. The resource may not be `java.io.Closeable`, so it is more generic to allow user-specified cleanup. If you only have `java.io.Closeable`s and you are either certain that the resource will create itself without exception, or you want that exception to propagate, then your code is fine. – Rex Kerr Dec 04 '13 at 11:46
  • Nice, but isn't it like you created two try blocks to achieve the same, the resource is created in outer try block and cleaned up in inner try block, if you refactor the inner try block to another method, you will never know about that in the outer try method. am I missing something ? – skjagini Mar 07 '19 at 19:36
  • C# has something like using(val resource = ...) {.. } and the compiler would call the dispose automatically before existing even if there is exception, wish Scala had something similar – skjagini Mar 07 '19 at 19:39
  • 1
    @skjagini - It's getting one in 2.13 (as a library method that is a careful elaboration of the kind of pattern above). – Rex Kerr Apr 01 '19 at 19:09
17

Have a look at Scala-ARM

This project aims to be the Scala Incubator project for Automatic-Resource-Management in the scala library ...

... The Scala ARM library allows users to ensure opening closing of resources within blocks of code using the "managed" method. The "managed" method essentially takes an argument of "anything that has a close or dispose method" and constructs a new ManagedResource object.

Community
  • 1
  • 1
Derek Wyatt
  • 2,697
  • 15
  • 23
0

Alternatively you can do this with Choppy's Lazy TryClose monad.

val output = for {
  fin   <- TryClose(in)
  fout  <- TryClose.wrapWithCloser(new FileOutputStream(path))(out => {out.flush(); out.close();})
} yield wrap(Iterator.continually(fin.read).takeWhile(-1 != _).foreach(fout.get.write))

// Then execute it like this:
output.resolve

More info here: https://github.com/choppythelumberjack/tryclose

(just be sure to import tryclose._ and tryclose.JavaImplicits._)