4

I'm just trying to understand a little more about Kotlin nullable type declarations. The type declaration of MutableList.remove is:

fun <T> MutableCollection<out T>.remove(element: T): Boolean

However the following compiles and runs even though the inferred type of myBook is Stuff? and its value is null.

data class Stuff(val name: String)

fun main(args: Array<String>) {
    val myListOfStuff: ArrayList<Stuff> = arrayListOf(
            Stuff("bed"),
            Stuff("backpack"),
            Stuff("lunch")
    )
    val myBook = myListOfStuff.find { it.name == "book" }
    val found = myListOfStuff.remove(myBook)
    println(myListOfStuff)
}

Why doesn't the remove type declaration use the nullable T? type, something like this?

fun <T> MutableCollection<out T>.remove(element: T?): Boolean

Or perhaps more precisely how does the out modifier make it possible for T to be nullable?

mjhm
  • 16,497
  • 10
  • 44
  • 55
  • Now, just wondering, can the `MutableList` contain a null? Or that's a no go? The remove method gets described at, removes an item from the list, if it is present (and returns true if it did), so I guess having a null there might not be problematic? – Icepickle Apr 27 '19 at 23:01
  • If I declared it as `ArrayList` it could contain nulls, but not as written. – mjhm Apr 27 '19 at 23:03

2 Answers2

4

I guess, you are not actually calling the member function remove you refer to but rather the following extension function:

fun <T> MutableCollection<out T>.remove(element: T): Boolean
  • Ahh I think you're on the right track. Could you spell this out more explicitly? It's not very clear to me how to connect the `out` modifier to the original question. – mjhm Apr 28 '19 at 14:49
  • @mjhm everything works the way you would expect it. so, your thinking is completely correct. you're just confused because an extension function is called and not the function you expected to be called. if you use intellij: hold ctrl and click on the remove function. the ide will bring you to the code definition which can be found in CollectionsKt.class – Willi Mentzel Apr 28 '19 at 20:24
  • Thanks for answering, I modified my original question. What I was asking seems to be confusing since I had the wrong function signature. – mjhm Apr 28 '19 at 23:21
3

The type T in

  fun <T> MutableCollection<out T>.remove(element: T): Boolean

is inferred as Stuff?. Your line

  val found = myListOfStuff.remove(myBook)

tries to remove all occurrences of null as myBook is null. It obviously can't find any but the compiler does not mind.

The out keyword ensures to the compiler that no functions like add(element) will be called (add(null) would be a problem), just functions that return T like get().

See this to clarify:

data class Stuff(val name: String)

fun main(args: Array<String>) {
    val myListOfStuff: ArrayList<Stuff?> = arrayListOf(
            Stuff("bed"),
            Stuff("backpack"),
            Stuff("lunch"),
            null
    )
    println(myListOfStuff)
    val found = myListOfStuff.remove(null)
    println(myListOfStuff)
} 

Output:

[Stuff(name=bed), Stuff(name=backpack), Stuff(name=lunch), null]
[Stuff(name=bed), Stuff(name=backpack), Stuff(name=lunch)]

For the out keyword see here: https://kotlinlang.org/docs/reference/generics.html#declaration-site-variance

Nemo
  • 728
  • 1
  • 4
  • 16
  • 1
    So you're saying that the compiler infers T as being String? first, then the function can be applied to the MutableList because the out covariance makes so that MutableList be a supertype of MutableList, since String? is a supertype of String. Did I get this right? So remove will use the comparison from the supertype class? – Eduardo macedo Apr 30 '19 at 02:58
  • 1
    If I understood you correctly then yes, you got this right. But I don't understand what you mean with "So remove will use the comparison from the supertype class?". – Nemo Apr 30 '19 at 11:24
  • I think I got it. Since `T` will be inferred as `String?`, the remove function can call `T get()` on the `MutableCollection` and assign it to a `String?` variable and then make the comparison with the parameter passed (which is also `String?`). Then maybe I'm wrong, type covariance is crazy. – Eduardo macedo May 01 '19 at 01:51
  • Correct, just that it's `Stuff?` and not `String?`. – Nemo May 01 '19 at 07:02