'Is Kotlin's runCatching..also equivalent to try..finally?

I want to run cleanup code after a certain block of code completes, regardless of exceptions. This is not a closeable resource and I cannot use try-with-resources (or Kotlin's use). In Java, I could do the following:

try {
  // ... Run some code
} catch(Exception ex) {
  // ... Handle exception 
} finally {
  // ... Cleanup code
}

Is the following Kotlin code equivalent?

runCatching {
  // ... Run some code
}.also {
  // ... Cleanup code
}.onFailure {
  // ... Handle exception
}

Edit: added boilerplate exception handling - my concern is with ensuring the cleanup code runs, and maintainability.



Solution 1:[1]

There is one important difference, where the code inside runCatching contains an early return. A finally block will be executed even after a return, whereas also has no such magic.

This code, when run, will print nothing:

fun test1()
    runCatching {
        return
    }.also {
        println("test1")
    }
}

This code, when run, will print "test2":

fun test2() {
    try {
        return
    } finally {
        println("test2")
    }
}

Solution 2:[2]

There is one big difference between both code samples. try...finally propagates exceptions while runCatching().also() catches/consumes them. To make it similar you would have to throw the result at the end:

runCatching {
  // ... Run some code
}.also {
  // ... Cleanup code
}.getOrThrow()

But still, it is not really 1:1 equivalent. It catches all exceptions just to rethrow them. For this reason, it is probably less performant than simple try...finally.

Also, I think this is less clear for the reader. try...finally is a standard way of dealing with exceptions. By using runCatching() just to immediately rethrow you actually confuses whoever will read this code later.

Your question sounded a little like you believed Kotlin does not have try...finally and you need to search for alternatives. If this is the case, then of course Kotlin has try...finally and I think you should use it instead of runCatching().

Solution 3:[3]

As per Kotlin's doc for runCatching:

Calls the specified function block and returns its encapsulated result if invocation was successful, catching any Throwable exception that was thrown from the block function execution and encapsulating it as a failure.

Even if finally always runs after a try block and also always runs after a runCatching, they do not serve the same purpose.

finally doesn't receive any argument and cannot operate on the values of the try block, while also receives the Result of the runCatching block.

TLDR; .runCatching{}.also{} is a more advanced try{}finally{}

Solution 4:[4]

There is also a difference in what is the result of evaluating the expression.

Consider the following code:

fun main() {
    val foo = try { 
        throw Exception("try")
    } catch(e: Exception) {
        "catch"
    } finally {
        "finally"
    }

    val bar = runCatching{
        throw Exception("runCatching")
    }.also{
        "also"
    }.onFailure {
        "onFailure"
    }

    println(foo)
    println(bar)
}

The output will be:

catch
Failure(java.lang.Exception: runCatching)

https://pl.kotl.in/a0aByS5l1

EDIT:

An interesting article that points out some differences as well:

https://medium.com/@mattia23r/a-take-on-functional-error-handling-in-kotlin-515b67b4212b

Now let’s give a second look at the implementation of runCatching in the gist above. What does it do? It catches everything.

In this case, it goes even further: it catches all Throwables. For those not knowing, Throwable is everything that can go after a throw keyword; it has two descendants: Exceptions and Errors. We haven’t mentioned Errors so far; Errors usually represent something wrong that happened at a lower level than your business logic, something that can’t usually be recovered with a simple catch.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Sam
Solution 2
Solution 3 Orijhins
Solution 4