Reputation: 2122
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
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