3
open class A
class B: A()

fun <T> copy(src: MutableList<T>, dst: MutableList<T>) {
    for (i in 0 until src.size) {
        dst.add(i, src[i])
    }
}

For the above mentioned code I understand that copy function expects both type parameters of exactly same type. With a slight modification copy(src: MutableList<T>, dst: MutableList<in T>) notice the in keyword, I am saying that src must be of exactly type T but destination can be of type T or any super type of T.

For the above modified code, I am able to call the method as following,

fun main(args: Array<String>) {
    val l1 = mutableListOf(B(), B())
    val l2 = mutableListOf<A>()
    copy(l1, l2)
} // main

The above copy(l1, l2) does not work if I remove in from the destination (understood).

My question is, I am able to call the function without any error if update the function parameter src to accept out projection of the list. e.g.

fun <T> copy(src: MutableList<out /*notice out here*/ T>, dst: MutableList<T>) {
    for (i in 0 until src.size) {
        dst.add(i, src[i])
    }
}

In this case, I am not able to understand what goes on under the hood.Can any one explain please?

Note that this is just an example from the book. I know I can use List instead of immutable list in src

mallaudin
  • 4,744
  • 3
  • 36
  • 68

2 Answers2

6

since you're using the function in only one way you should use the use-site variance modifiers anyways to make it clear to the caller that you may add to dst and get data from src:

fun <T> copy(src: MutableList<out T>, dst: MutableList<in T>) {
    for (i in 0 until src.size) {
        dst.add(i, src[i])
    }
}

Further, since src is really used as a List rather than a MutableList, you should prefer it accordingly. As a result, you won't need the out modifier anymore since List already defined its type parameter T as out only:

fun <T> copy(src: List<T>, dst: MutableList<in T>)

In response to your question: The problem actually happens when you call copy with two differently typed lists in your main, once with MutableList<A> and once with MutableList<B>. The compiler cannot infer whether the type of copy shall be A or B. To fix this, you need to give more information:

1) When you set dst to MutableList<in T>, the compiler knows that you will only add T types based on src to it (in your example this is B).

2) When you set src to MutableList<out T>, the compiler understands that you will only add T and its subtypes to dst which is fine as well (in this case T will be inferred as A though).

s1m0nw1
  • 76,759
  • 17
  • 167
  • 196
  • 2
    Agree with everything in @s1m0nw1's answer above. Something that you might find useful is reading the [official documentation page about generics](https://kotlinlang.org/docs/reference/generics.html), and notice their distinction between **producers** and **consumers**. Basically, something marked `out` is produced (hence immutable list use `out`, as they only produce values, not consume them); something marked `in` is consumed. – Yoni Gibbs Nov 05 '18 at 10:10
  • Thanks @s1m0nw1. `in this case T will be inferred as A though` got it now. – mallaudin Nov 05 '18 at 10:17
1

out here works symmetrically to in:

in keyword, I am saying that src must be of exactly type T but destination can be of type T or any super type of T

So now you are saying that src must be a MutableList of type T or any subtype of T, while dst must be a MutableList of exactly type T.

So when you have l1: MutableList<B> and l2: MutableList<A>, the compiler infers the type parameter in copy(l1, l2) as copy<A>(l1, l2), and it typechecks: MutableList<B> is a subtype of MutableList<out A>.

Because you are only using out-compatible operations on src, and only in-compatible operations on dst, as @s1m0nw1 says it makes perfect sense to include both modifiers.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487