in and out

To further customize your generic type parameters, Kotlin provides the keywords in and out. To see how they work, create a simple generic Barrel class in a new file called Variance.kt:

Listing 17.15  Defining Barrel (Variance.kt)

class Barrel<T>(var item: T)

To experiment with Barrel, add a main function. In main, define a Barrel to hold a Fedora and another Barrel to hold Loot:

Listing 17.16  Defining Barrels in main (Variance.kt)

class Barrel<T>(var item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> = Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))
}

While a Barrel<Loot> can hold any kind of loot, the particular instance defined here happens to hold a Coin (which, remember, is a subclass of Loot).

Now, assign fedoraBarrel to lootBarrel:

Listing 17.17  Attempting to reassign lootBarrel (Variance.kt)

class Barrel<T>(var item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> = Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))

    lootBarrel = fedoraBarrel
}

You may be surprised to find that the assignment was not allowed by the compiler (Figure 17.1):

Figure 17.1  Type mismatch

Type mismatch

It might seem like the assignment should have been possible. Fedora is, after all, a descendant of Loot, and assigning a variable of the Loot type an instance of Fedora is possible:

    var loot: Loot = Fedora("a generic-looking fedora", 15) // No errors

To understand why the assignment fails, let’s walk through what could happen if it succeeded.

If the compiler allowed you to assign the fedoraBarrel instance to the lootBarrel variable, lootBarrel would then point to fedoraBarrel, and it would be possible to interface with fedoraBarrel’s item as Loot, instead of Fedora (because of lootBarrel’s type, Barrel<Loot>).

For example, a coin is valid Loot, so it would be possible to assign a coin to lootBarrel.item (which points to fedoraBarrel). Do so in Variance.kt:

Listing 17.18  Assigning a coin to lootBarrel.item (Variance.kt)

class Barrel<T>(var item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> = Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))

    lootBarrel = fedoraBarrel
    lootBarrel.item = Coin(15)
}

Now, suppose you tried to access fedoraBarrel.item, expecting a fedora:

Listing 17.19  Accessing fedoraBarrel.item (Variance.kt)

class Barrel<T>(var item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> = Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))

    lootBarrel = fedoraBarrel
    lootBarrel.item = Coin(15)
    val myFedora: Fedora = fedoraBarrel.item
}

The compiler would then be faced with a type mismatch – fedoraBarrel.item is not a Fedora, it is a Coin – and you would be faced with a ClassCastException. This is the problem that arises, and the reason the assignment is not allowed by the compiler.

It is also why the in and out keywords exist.

In the Barrel class’s definition, add the out keyword and change item from a var to a val:

Listing 17.20  Adding out (Variance.kt)

class Barrel<out T>(varval item: T)
...

Next, delete the line that assigned Coin to item (which is no longer allowed, since item is a val) and change the assignment of myFedora to lootBarrel.item instead of fedoraBarrel.item.

Listing 17.21  Changing the assignment (Variance.kt)

class Barrel<out T>(val item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> = Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))

    lootBarrel = fedoraBarrel
    lootBarrel.item = Coin(15)
    val myFedora: Fedora = fedoraBarrel.itemlootBarrel.item
}

All errors are resolved. What has changed?

There are two roles a generic parameter can be assigned: producer or consumer. The role of producer means that a generic parameter will be readable (but not writable), and consumer means the generic parameter will be writable (but not readable).

When you added the out keyword to Barrel<out T>, you specified that the generic would act as a producer – that it would be readable, but not writable. That meant that defining item with the var keyword was no longer permitted – otherwise, it would not simply be a producer of Fedoras, but would also be writable and support consuming one.

By making the generic a producer, you assure the compiler that the dilemma pointed out earlier is no longer a possibility: Since the generic parameter is a producer, not a consumer, the item variable will never change. Kotlin now permits the assignment of fedoraBarrel to lootBarrel, because it is safe to do so: lootBarrel’s item now has type Fedora, not Loot, and cannot be changed.

Take a closer look at the assignment of the myFedora variable in IntelliJ. The green shading around lootBarrel indicates that a smart cast took place, and that is confirmed when you mouse over it (Figure 17.2):

Figure 17.2  Smart cast to Barrel<Fedora>

Smart cast to Barrel<Fedora>

The compiler can smart cast Barrel<Loot> to Barrel<Fedora> because item can never change – it is a producer only.

By the way, Lists are also producers. In Kotlin’s definition for List, the generic type parameter is marked with the out keyword:

    public interface List<out E> : Collection<E>

Marking the generic type parameter for Barrel with the in keyword would have the opposite effect on reassigning the Barrels: Instead of being allowed to assign fedoraBarrel to lootBarrel, you would be allowed to assign lootBarrel to fedoraBarrel – but not vice versa.

Update Barrel to use the in keyword instead of out. You will notice that Barrel will now require dropping the val keyword for item, since it could otherwise produce an item (a violation of the consumer role).

Listing 17.22  Marking Barrel with in (Variance.kt)

class Barrel<inout T>(val item: T)
...

Now, lootBarrel = fedoraBarrel in main has an error warning you of a type mismatch. Reverse the assignment:

Listing 17.23  Reversing the assignment (Variance.kt)

...
fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> = Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))

    lootBarrel = fedoraBarrel
    fedoraBarrel = lootBarrel
    val myFedora: Fedora = lootBarrel.item
}

The opposite assignment is possible because the compiler can be certain you would never be able to produce Loot from a Barrel containing Fedoras – leading to the possibility of class cast exceptions.

You removed the val keyword from Barrel because Barrel is now a consumer – it accepts a value, but it does not produce one. Therefore, you also drop the item lookup. This is how the compiler is able to reason that the assignment you have made is a safe one.

By the way, you may have heard the terms covariance and contravariance used to describe what out and in do. In our opinion, these terms lack the commonsense clarity of in and out, so we avoid them. We mention them here because you may encounter them elsewhere, so now you know: If you hear “covariance,” think “out,” and if you hear “contravariance,” think “in.”

In this chapter you have learned how to use generics to expand the capabilities of Kotlin’s classes. You have also seen type constraints and how the in and out keywords can be used to define the producer or consumer role for the generic parameter.

In the next chapter, you will learn about extensions, which allow you to share functions and properties without using inheritance. You will use them to improve NyetHack’s codebase.