Home > Enterprise >  Returning from finally block
Returning from finally block

Time:04-23

I have Java background and recently I started to learn Kotlin. Right now I'm reading a book "Programming Kotlin" and I reached a snippet with try-catch expression. It caused me to write some simple function to compare the way it works in Java and in Kotlin. First function:

fun tryExpExplicit(throwing: Boolean): Int {
    return try {
        if (throwing) {
            throw RuntimeException()
        }
        return 1
    } catch (e: Exception) {
        return 2
    } finally {
        return 3
    }
}

works as I expected and always returns 3.

Unexpectedly, when I use implicit return, the behaviour is different

fun tryExpImplicit(throwing: Boolean): Int {
    return try {
        if (throwing) {
            throw RuntimeException()
        }
        1
    } catch (e: Exception) {
        2
    } finally {
        3
    }
}

and 3 is never returned.
Why do these two functions work differently?

CodePudding user response:

The difference from behaviour in Java is because try ... catch ... finally is an expression in Kotlin, not a statement.

The way that a "try-expression" is evaluated, is defined as follows in the specification:

The try-expression evaluation evaluates its body; if any statement in the try body throws an exception (of type E), this exception, rather than being immediately propagated up the call stack, is checked for a matching catch block. If a catch block of this try-expression has an exception parameter of type T:>E , this catch block is evaluated immediately after the exception is thrown and the exception itself is passed inside the catch block as the corresponding parameter. [...]

If there is a finally block, it is evaluated after the evaluation of all previous try-expression blocks

The value of the try-expression is the same as the value of the last expression of the try body (if no exception was thrown) or the value of the last expression of the matching catch block (if an exception was thrown and matched). All other situations mean that an exception is going to be propagated up the call stack, and the value of the try-expression is undefined.

Note: as described, the finally block (if present) is always executed, but has no effect on the value of the try-expression.

So when throwing is true, the try, catch, and finally blocks are all executed, but the value of the try-expression is the value of the last expression in the catch block. This explains the behaviour in both the "explicit" and "implicit" cases.

In the "explicit return" case, return 2 is executed, but the method can't return there - the finally block still has to run! Then return 3 is executed, and now the method returns. Notably, the outer return is never executed. You can delete the outer return and start the method with try { ... and get the same result.

In the "implicit return" case, 2 is evaluated, and no side effects happen since it is just a literal. Then the finally block runs, and 3 is evaluated, and again no side effects happen since it is just a literal. Now we have finished evaluating the whole try-expression, and according to the spec, the value of the expression should be what we evaluated in the catch block - i.e. 2. And now we execute return 2.

Side note: return ... are also expressions, and they have the type of Nothing (the subtype of every type), because they never evaluate to anything. This is why you are able to write return 1 etc as the last line in the try-expression blocks in the "explicit return" case. In that case, the try-expression actually has a type of Nothing, and the outer return is actually returning Nothing. This is fine, because Nothing is a subtype of Int.

CodePudding user response:

In the documentation here, it says that when try/catch is used an expression:

The contents of the finally block don't affect the result of the expression.

In the first example, even though your finally block is not affecting the expression's evaluation, it's short-circuiting the expression entirely by returning directly.

In the second example, finally isn't doing anything, and since it doesn't affect the expression, the value is ignored.

In practice, you'd never write code to return from a try and/or catch block if you're returning from a finally block, since it would be useless code.

CodePudding user response:

You can do something like this in kotlin

fun tryExpExplicit(throwing: Boolean): Int {
    return runCatching {
        // do something
    }.onFailure { exception ->
        // similar to catch
    }.onSuccess { data ->
        // similar to finally
    }.getOrThrow() // get the data or throw
}

internally runCatching is using try/catch blocks similar in java. you can think of

  • runCatching as try
  • .onFailure as catch
  • .onSuccess as finally

you could also use it's extension to return the value

  • .getOrThrow if you want to return the data or throw an exception
  • .getOrNull if you want to return the data or null if it throw an exception
  • .getOrElse if you want to return the data or else if it throw an exception
  • .getOrDefault if you want to return the data or default if it throw an exception
  • Related