0

I'm trying to understand Kotlin take of generics especially with the in and out keywords. I've written this simple piece to hopefully illustrate my point of confusion.

This is the method that looks and works OK;

enter image description here

Here, IDE gives a hint saying that the type of the item the for loop is iterating over is a Shape. This is good.

Another version of the method above that doesn't seem quite right at first glance (since it's a "consumer"). But all I want to do is to read a list of shapes and that's all what's in this from.

enter image description here

That doesn't work which is maybe right according to the rules but I am not able to connect the dots here. It seems like a safe operation. I can also see Kotlin downs the type to "Any?" which explains why this gives a compiler error already. Can someone explain the potential type unsafety here please?

stdout
  • 2,471
  • 2
  • 31
  • 40
  • "Kotlin provides a complementary variance annotation: in. It makes a type parameter contravariant: it can only be consumed and never produced" – Tim Feb 26 '19 at 16:35
  • @TimCastelijns Thanks but I want to understand the reasoning behind it. Again, It looks like a safe operation to me. Or I'm wrong on this but in that case I really need to an example that visualise how this usage breaks type-safety dynamics of the language. – stdout Feb 26 '19 at 16:43

1 Answers1

5

Variance is always a tough concept to work through…

If you're using the terms ‘producer’ and ‘consumer’, note that they refer to your interaction with the item in question ‚ in this case, the MutableList parameter.

Your first function, with a MutableList<out Shape> parameter, can accept a list of Shapes, or a list of any subclass of Shape.  (out means roughly ‘or any subclass’.)

In each case, if you're consuming the list, whatever you pull out of the list will always be a Shape (as the IDE confirms).

But if you want to put something into the list (as a producer), you can't tell what would be safe to add, as that would depend on whether it's a subclass, and which one.

Your second function, with a MutableList<in Shape> parameter, is the opposite: it can accept a list of Shapes, or a list of any superclass of Shape.  (in means roughly ‘or any superclass’.)

In each case, it's always safe to put a Shape into the list (as a producer).  But you can't tell anything about the type of what you pull out of it (as a consumer): it could be called with a MutableList<Any?>, which could contain Strings or Files or whatever.  So the compiler will give it the universal type, Any? (as the IDE confirms).

gidds
  • 16,558
  • 2
  • 19
  • 26
  • "Your second function, with a MutableList parameter, is the opposite: it can accept a list of Shapes, or a list of any superclass of Shape" - Having read your post, I exactly thought the same thing. So, you can pass a list of Any, or maybe list of Foo (assuming Shape extends a Foo class) through that parameter. None of them represent Shape objects which would break the type safety. – stdout Feb 27 '19 at 11:09