
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) } }
- It is catching all exceptions, including
OutOfMemoryErroror 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.
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) } }
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) } }
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) } }
Finally, we can add a contract that specifies that the
block is called exactly once. It will add some smartness to this function:
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) } }