article banner (priority)

Improved runCatching

I have many issues with runCatching from Kotlin stdlib. Not that it isn't useful, there is certainly a need for it, the issue is that it is poorly implemented. This is its current implementation:
inline fun <R> runCatching(block: () -> R): Result<R> { return try { Result.success(block()) } catch (e: Throwable) { Result.failure(e) } }
Now here are the issues I have with it:
  • It is catching all exceptions, including OutOfMemoryError or other unrecoverable ones. It is a known anit-pattern.
  • It is catching exceptions used to signal cancellation.
  • It lacks a contract that would allow the compiler to better reason about its usages.
Let's fix those issues!
First, we should catch Exception instead of Throwable. Throwable covers Error and Exception. Error shouldn't be caught as it is used to signal unrecoverable errors.
inline fun <R> runCatching(block: () -> R): Result<R> { return try { Result.success(block()) } catch (e: Exception) { // <- Exception instead of Throwable Result.failure(e) } }
Second, we deal with exceptions used to signal cancellations. In Kotlin Coroutines it is CancellationException, in JVM stdlib it is InterruptedException. They are both a subtype of Exception, so we must catch and throw them above Exception. Typically, KMP projects and projects that use Kotlin coroutines only support CancellationException:
inline fun <R> runCatching(block: () -> R): Result<R> { return try { Result.success(block()) } catch (e: CancellationException) { throw e } catch (e: Exception) { Result.failure(e) } }
Teams that don't use Kotlin coroutines often only support InterruptedException:
inline fun <R> runCatching(block: () -> R): Result<R> { return try { Result.success(block()) } catch (e: InterruptedException) { throw e } catch (e: Exception) { Result.failure(e) } }
For our example, we will support both, though I rarely see that in practice.
Finally, we can add a contract that specifies that the block is called exactly once. It will add some smartness to this function:
Now let's discuss the name. I can see some teams naming it just runCatching, and remembering to import it. I prefer giving it a different name for clarity. My suggestion is runCatchingCancellable. Here is the complete function:
@OptIn(ExperimentalContracts::class) inline fun <T> runCatchingCancellable(block: () -> T): Result<T> { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return try { Result.success(block()) } catch (e: CancellationException) { throw e } catch (e: InterruptedException) { throw e } catch (e: Exception) { Result.failure(e) } }
If you want to understand cancellation and dive deep into Kotlin Coroutines, join the Coroutines Mastery cohort course. We will have a lot of fun there!