Kotlin Result when awaited fails with exception while running in "runBlocking"

Viewed 518

Please see the code below, the main function with suspend modifier works fine, but when ran with run blocking the await behaves prematurely and the Result is returned with the coroutine object instead of actual value.

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

typealias SuspendingResultFunction<T, A> = suspend (T) -> Result<A>

suspend fun <T, R> Result<T>.then(transform: SuspendingResultFunction<T, R>): Result<R> {
    return if (this.isSuccess) transform(this.getOrNull()!!)
    else Result.failure(this.exceptionOrNull()!!)
}

private suspend fun suspendingFunction(no: Int): Result<Int> {
    val plusOneTask = CoroutineScope(Dispatchers.Default).async { delayedPlusOne(no) }
    val plusOne = plusOneTask.await()
    return Result.success(plusOne)
}

private suspend fun delayedPlusOne(no: Int): Int {
    delay(1000L)
    return no + 1
}

suspend fun main_with_suspend_works_good() {
    val threeResult = suspendingFunction(1).then { suspendingFunction(it) }
    val three = threeResult.getOrNull()
    println(three)
}

fun main() {
    runBlocking {
        val threeResult = suspendingFunction(1).then { suspendingFunction(it) }
        val three = threeResult.getOrNull()
        println(three)
        // above line throws the exception, once the execution reach to await.
        // : class kotlin.coroutines.intrinsics.CoroutineSingletons cannot be cast to class java.lang.Number (kotlin.coroutines.intrinsics
    }
    // When we use junit for testing we suppose to use runBlocking !!
    // But even why runBlocking does not work ?
}
2 Answers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

typealias SuspendingResultFunction<T, A> = suspend (T) -> Result<A>

suspend fun <T, R> Result<T>.then(transform: SuspendingResultFunction<T, R>): Result<R> {
    return if (this.isSuccess) transform(this.getOrNull()!!)
    else Result.failure(this.exceptionOrNull()!!)
}

private suspend fun suspendingFunction(no: Int): Result<Int> {
    val plusOneTask = CoroutineScope(Dispatchers.Default).async { delayedPlusOne(no) }
    val plusOne = plusOneTask.await()
    return Result.success(plusOne)
}

private suspend fun delayedPlusOne(no: Int): Int {
    delay(1000L)
    return no + 1
}

suspend fun main_works_good() {
    val threeResult = suspendingFunction(1).then { suspendingFunction(it) }
    val three = threeResult.getOrNull()
    println(three)
}

fun main() {
    runBlocking {
        val threeResult = suspendingFunction(1).then { suspendingFunction(it) }
        val three = threeResult.getOrNull()
        println(three)
        // above line throws the exception, once the execution reach to await.
        // : class kotlin.coroutines.intrinsics.CoroutineSingletons cannot be cast to class java.lang.Number (kotlin.coroutines.intrinsics
    }
    // When we use junit for testing we suppose to use runBlocking !!
    // But even why runBlocking does not work ?
}

sealed class Result<out A> {

    companion object {
        fun <T> success(t: T) = Success(t)
        fun failure(ex: Throwable) = Failure(ex)
    }

   
    inline fun <T> match(onSuccess: (A) -> T, onFailure: (Throwable) -> T): T =
    when (this) {
        is Success<A> -> onSuccess(this.data)
        is Failure -> onFailure(this.ex)
    }

    inline fun <T> map(onSuccess: (A) -> T): Response<T> = match({ 
    success(onSuccess(it)) }, { failure(it) })

    inline fun <T> flatMap(onSuccess: (A) -> Response<T>): Response<T> 
    = match(onSuccess) { failure(it) }

    fun getOrNull(): A? = this.match({ it }, { null })

    fun exceptionOrNull(): Throwable? = this.match({ null }, { it })

    private fun isGood() = this is Success<*>

    val isSuccess = isGood()

    val isFailure = !isGood()
}

// this needs to be outside of the result class because this uses input variance !
inline fun <T> Result<T>.getOrElse(orElse: (Throwable) -> T): T = this.match({ it }, orElse)

data class Success<A>(val data: A) : Result<A>()
data class Failure(val ex: Throwable) : Result<Nothing>()

`

I suspected Kotlin.Result type, so tried to implement by own Result Type, it works good.

Update: Perhaps this is an issue with all inline classes not limited to Kotlin Result, It is better to respect Kotlin compiler warning and not use Result as return types.

Related issue in here - youtrack.jetbrains.com/issue/KT-30223

Related