Confused about automatic out variance

I have something like this:

sealed class Event {
    object A : Event()
    object B : Event()
}

data class ManuItem<T>(
    val event: T
)

val MENU_ITEMS = listOf(
    ManuItem(Event.A),
    ManuItem(Event.B)
)

val items: List<ManuItem<Event>> = MENU_ITEMS

I get notified about a type mismatch in the last code line. It says:

Type mismatch.
Required:
List<MenuItem<Event>>
Found:
List<MenuItem<out Event>>

Why is the Event automatically declared as out-variant.

Further, if i change the code as follows:

data class ManuItem<T>(
    var event: T
)

I am as expected not able to declare the type parameter of MenuItem as out-variant, but the error stys the same:

Type mismatch.
Required:
List<MenuItem<Event>>
Found:
List<MenuItem<out Event>>

Can anybody explain, why this happens?

Can be detected as MenuItem<out Event> or MenuItem<Event>, since the former is more general that is picked. There might be a more formal explanation somewhere, but that’s roughtly it.

You can specify a type you prefer if you wish to:

and that works.

Thank you for your response :slight_smile:

So in general, the more explicit type will be picked?

What i still don’t understand is:
If MenuItem<out Event> or MenuItem<Event> are detected if the class MenuItem is immutable,
shouldn’t only MenuItem<Event> be detected if the generic property of MenuItem is mutable as then the type parameter T is produced and consumed.

data class MenuItem<T>(
    var event: T // <-- now mutable, but still same error
)

val/var does not at all affect the variance. It is the opposite. You can have MenuItem<out Event> with var inside and then it will effectively become val.

Anyway, you can change your data class to:

data class ManuItem<out T>(
    val event: T
)

And then your code will work.

You can also read my deleted post for some insights on what is going on here. I removed it because I was incorrect about my main point (list is actually MutableList<Animal>). But generally, I believe this is the right direction.

Thank you aswell for your response :slight_smile:

Are you sure about val/var not affecting the variance?
because var is mutable and therefore in an invariant position. Using var, MenuItem can consume T.
Also, the compiler will fail with code like this:

data class MenuItem<out T>(
    var event: T // <-- Type parameter T is declared as 'out' but occurs in 'invariant' position in type T
)

And as the compiler fails at the mentioned code, i wonder why this

data class MenuItem<T>(
    var event: T
)

becomes detected as MenuItem<out T> if i am not able to declare it like this by myself

Above is disallowed, because it doesn’t make too much sense. If MenuItem can only produce T objects and can’t consume them, then event can’t be ever set, making it effectively similar to val.

However, it makes sense that the class that both produces and consumes T objects, is used in the context where it is restricted to only produce or only consume. In such case use-site variance disallows to produce/consume items by the class that normally could do both.

Also, it would be pretty crazy that whenever we use MenuItem, the compiler would read its all members to determine if it can be used with out or in.

1 Like