user826955
user826955

Reputation: 3206

Uninitialized variable (block cannot contain declarations)

Following these examples and especially this code:

object Control {
  def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B =
    try {
      f(resource)
      } finally {
        resource.close()
    }
}

...

using(io.Source.fromFile("example.txt")) { source => { .....

I wanted to extend the using method so instead of a type which implements close it receives a string (filename), a function to open a source, and the processing function. In this way, I would avoid the exception which would be thrown in the above example in case the given file does not exist.

So I ended up with this code:

object Control
{
   def using[A <: { def close(): Unit }, B](opener: String => A)(name:String)(func: A => B): Unit =
   {
      var resource:A
                // ^ Error: 'Block cannot contain declarations'

      try
      {
         resource = opener(name)
         func(resource)
      }
      catch
      {
         case e: (_) => println(s"Failed to open resource '${name}' (${e})")
      }
      finally
      {
         println("Closing file ...")
         resource.close()
      }
   }
}

So I am defining a method, which takes as first parameter an opener-function, which receives a string, and returns an object which implements close, a string (for the opener function), and a processing function.

However it won't let me declare the resource variable outside of the try-catch block (so I can reach it in the finally block). It will work if I just put it into the try block like var resource:A = opener(name), however then I cannot reach resource in the finally block.

How can I solve it? I have to say that I am still a beginner in Scala, so I am a bit lost here.

Upvotes: 0

Views: 389

Answers (1)

stefanobaghino
stefanobaghino

Reputation: 12804

Here is a revised example that you can also run on Scastie:

import scala.util.control.NonFatal
import scala.language.reflectiveCalls

type Resource = { def close(): Unit }

def using[A <: Resource, B](opener: String => A)(name: String)(func: A => B): Unit = {

  var resource = null.asInstanceOf[A]

  try {
     resource = opener(name)
     func(resource)
  } catch {
     case NonFatal(e) => println(s"Failed to open resource '${name}' (${e.getMessage})")
  } finally {
     println("Closing resource...")
     resource.close()
  }

}

final class SomeKindOfResource(n: String) {

  def use(): Int = n.toInt

  def close(): Unit = {}

}

using(new SomeKindOfResource(_))("42")(n => println(n.use()))
using(new SomeKindOfResource(_))("NaN")(n => println(n.use()))

The piece that you were lacking is that initialization:

var resource = null.asInstanceOf[A]

Please note that despite what you may think, this does not throw a NullPointerException. You can read more about it here.

I've added a few more things you may be interested in:

  • explicitly importing scala.language.reflectiveCalls: structural typing is achieved at runtime through reflective calls (on the JVM, at least) and the compiler will tell you at compile time with a warning

  • naming the { def close(): Unit } to something that makes it a little bit more readable in the method signature using type

  • using NonFatal to handle exception (you can read more about it here)

Upvotes: 1

Related Questions