Sergei Bubenshchikov
Sergei Bubenshchikov

Reputation: 5371

Type safe usage of generic sealed classes

I found interesting thing when I write generic sealed class. Here is first version:

// sample interface and implementation
interface MyInterface
class MyInterfaceImpl : MyInterface

sealed class Response<T: MyInterface>               

data class Success<T: MyInterface>(val payload: T) : Response<T>()
data class Failure(val errorCode: Int) : Response<MyInterface>()
object Cancelled : Response<MyInterface>()

let's say we also have request function like this:

fun <T: MyInterface> requestObject(cls : KClass<T>): Response<T> = TODO("Request")

now at the use side we have errors:

fun test() = when (val response = requestObject(MyInterfaceImpl::class)) {
    is Success -> print("Payload is ${response.payload}")     // Smart cast perfectly works!
    is Failure -> print("Error code ${response.errorCode}")   // Incomparable types: Failure and Response<MyInterfaceImpl>
    Cancelled -> print("Request cancelled")                   // Incomparable types: Cancelled and Response<MyInterfaceImpl>
}

First question: Failure and Cancelled isn't use T for in/out positions, why this cast is unchecked and I need to suppress it?

After a while, Konstantin show me solution how to declare type safe sealed class:

sealed class Response<out T: MyInterface>                    // <-- out modifier here

data class Success<T: MyInterface>(val payload: T) : Response<T>()
data class Failure(val errorCode: Int) : Response<Nothing>() // <-- Nothing as type argument
object Cancelled : Response<Nothing>()                       // <-- Nothing as type argument

This declaration works like a charm and now I have questions:

Second question: why it's necessary to write out modifier here?

Third question: why Producer<Nothing> is subtype of Producer<MyInterface>? By definition of covariant: Producer<A> is subtype of Producer<B> if A subtype of B, but Nothing isn't subtype of MyInterface. It looks like undocumented extralinguistic feature.

Upvotes: 2

Views: 1447

Answers (1)

tynn
tynn

Reputation: 39853

The variance doesn't work out in the end. Response<MyInterfaceImpl> is not Response<MyInterface> and therefore Failure and Cancelled cannot be used. Eventhough you're not using the gerneric type, you still declare it.

When putting out T, you'll have an effect like ? extends T in Java.

Then for Nothing you have:

Nothing has no instances. You can use Nothing to represent "a value that never exists".

This also means it's a subtype of everything and thus also works for the generics.

Upvotes: 2

Related Questions