enyciaa
enyciaa

Reputation: 2122

Kotlin generics on sealed class won't compile with type check fails

Imagine we have a data structure like this

sealed class ResultOf {
    class Success<out T>(
        val value: T
    ) : ResultOf()

    class Failure(
        val exception: Exception
    ) : ResultOf()
}

This works fine:

inline fun <reified T> ResultOf.doIfFailure(callback: (exception: Exception) -> Unit) {
    if (this is ResultOf.Failure) {
        callback(exception)
    }
}

But the following won't compile with compile problems over ResultOf.Success & callback(value)

inline fun <reified T> ResultOf.doIfSuccess(callback: (value: T) -> Unit) {
    if (this is ResultOf.Success) {
        callback(value)
    }
}

One type argument expected. Use 'Success<*>' if you don't want to pass type arguments

I'm guessing this is something to do with type erasure? But can't quite find anything which explains why. Also, is there a way round this? (Without putting a generic on ResultOf)?

Upvotes: 1

Views: 437

Answers (1)

Tenfour04
Tenfour04

Reputation: 93639

Reified types only work in inline functions. Class types are never reified. At run-time, there is no way to tell what the type of Success<T> is.

You could manually track the type by adding a class parameter to the class constructor, and casting in your function like this.

sealed class ResultOf {
    class Success<out T: Any>(
            val cls: KClass<out T>,
            val value: T
    ) : ResultOf()

    class Failure(
            val exception: Exception
    ) : ResultOf()
}

inline fun <reified T> ResultOf.doIfSuccess(callback: (value: T) -> Unit) {
    if (this is ResultOf.Success<*> && cls == T::class) {
        callback(value as T)
    }
}

It's not a great design, though, because you could use doOnSuccess with the wrong value type and it would silently do nothing even on success. I would just put the type in the ResultOf class definition. Then you don't need to use a reified function or even do any casting:

sealed class ResultOf<out T> {
    class Success<out T>(
            val value: T
    ) : ResultOf<T>()

    class Failure(
            val exception: Exception
    ) : ResultOf<Nothing>()
}

inline fun <T: Any> ResultOf<T>.doIfSuccess(callback: (value: T) -> Unit) {
    if (this is ResultOf.Success<T>) {
        callback(value)
    }
}

Upvotes: 4

Related Questions