5

According to the source of Closable.use, if an error occurs, an exception will be thrown.

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }

In most examples of Closable.use, try-catch is not used as shown below. Why isn't error handling needed? Is it safe?

    BufferedReader(FileReader("test.file")).use { return it.readLine() }
user3752013
  • 189
  • 1
  • 8
  • 1
    This Q&A is for Java but the same concepts should apply in Kotlin: [When to catch the Exception vs When to throw the Exceptions?](https://stackoverflow.com/questions/18679090/when-to-catch-the-exception-vs-when-to-throw-the-exceptions). – Slaw Jun 02 '20 at 16:00
  • @Tenfour04 shouldn't the try/catch be enclosing the use function? – Diego Marin Santos Jun 02 '20 at 16:12
  • @Tenfour04 It's `use` which can throw the exception that I believe the OP is worried about. – Slaw Jun 02 '20 at 16:12
  • Putting try/catch inside `use` is like doing a traditional try/catch/finally. Putting it outside causes the exceptions to be handled *after* the object is closed. If the act of closing the object can itself throw an exception, then you need to catch that outside `use`. This behavior is similar to Java's try-with-resources. – Tenfour04 Jun 02 '20 at 16:51
  • @DiegoMarin Yes, you're right if we need to handle IOExceptions both from reading and from closing. – Tenfour04 Jun 02 '20 at 17:14
  • 1
    Interesting that `use` works differently than Java's `try`-with-resources when it comes to stacked exceptions from both the virtual `try` and `finally` blocks. `use` suppresses the exception from `close`, but `try`-with-resources suppresses the exception from `try`. – Tenfour04 Jun 02 '20 at 17:20
  • @Tenfour04 Maybe I'm misreading your comment, but Java's try-with-resources will [suppress](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Throwable.html#addSuppressed(java.lang.Throwable)) the exception thrown by `AutoCloseable#close()` (if, and only if, the code inside the try-block _also_ throws an exception). See [§14.20.3](https://docs.oracle.com/javase/specs/jls/se14/html/jls-14.html#jls-14.20.3) of the JLS. It looks like Kotlin does the exact same thing (at least since Kotlin 1.1.2). – Slaw Jun 03 '20 at 10:13
  • Where Kotlin seems to differ is that Java's try-with-resources has an ["extended" version](https://docs.oracle.com/javase/specs/jls/se14/html/jls-14.html#jls-14.20.3.2), allowing a user to add a catch-block which can catch either an exception thrown from the try-block or an exception thrown from closing the resource. – Slaw Jun 03 '20 at 10:19
  • Yes, by “stacked exceptions” I meant the case where they both throw. – Tenfour04 Jun 03 '20 at 11:33
  • @Tenfour04 I suppose my issue is with "_but try-with-resources suppresses the exception from try_". That's not true. It suppresses the exception from `close`. – Slaw Jun 03 '20 at 13:04

2 Answers2

5

This line

 BufferedReader(FileReader("test.file")).use { return it.readLine() }

is not safe. Reading and closing the reader can both throw IOExceptions, which are not RuntimeExceptions (caused by programming errors). That means leaving them uncaught exposes your app to crashing from things outside your control.

Since Kotlin doesn't have checked exceptions, the compiler won't warn you about this. To do this safely, you need to wrap it in try/catch. And if you want to handle read errors differently than close errors, you either need to have inner and outer try/catch statements:

try { 
    BufferedReader(FileReader("test.file")).use { 
        try {
            return it.readLine()
        catch (e: IOException) {
            println("Failed to read line")
        }
    }
} catch (e: IOException) {
    println("Failed to close reader")
}

or wrap the whole thing and extract any suppressed exceptions, but then its cumbersome to distinguish between them:

try {
    BufferedReader(FileReader("test.file")).use { return it.readLine() }
} catch (e: IOException) {
    val throwables = listOf(e, *e.suppressed)
    for (throwable in throwables)
        println(throwable.message)
}

But in practice, you're probably not going to react differently to various IOExceptions, so you can just put the one try/catch outside.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
4

We see from Kotlin documentation what is the purpose of the use function:

Executes the given block function on this resource and then closes it down correctly whether an exception is thrown or not.

This function closes the resource properly if the block function completed successfully or threw an exception. It is your responsibility to handle the result of the block function.

If an exception was thrown and there is a way to handle it and proceed with code execution, use a try/catch. If there is nothing to do about it and control should be passed to the caller, it is not necessary to use a try/catch.

Diego Marin Santos
  • 1,923
  • 2
  • 15
  • 29