2

Suppose I have a class Message and a class Channel<T : Message>.

Now, how come I can't cast Channel<out Message> to Channel<Message> without an Unchecked Cast warning?

Shouldn't this cast always be safe since Channel<out Message can only contain objects of type Message or subclasses of type Message?

Archmede
  • 1,592
  • 2
  • 20
  • 37

3 Answers3

3

No, and here is proof.

class Channel<T : Message> {
  fun send(t: T)
  fun receive(): T
}
class Apple : Message()
class AppleChannel : Channel<Apple>() {
  ...
}

val appleChannel = AppleChannel()
val outMessageChannel: Channel<out Message> = appleChannel // MUST WORK
val messageChannel: Channel<Message> = outMessageChannel 
   // you say this should work, but...
messageChannel.send(Orange()) 
   // sending orange on a channel that only knows how to send apples!  
   // Error!

The only consistent thing to do is to not allow the conversion of Channel<out Message> to Channel<Message> in this scenario.

If it were defined as Channel<out T: Message>, then this would be safe, but trying to define send would cause an error.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
2

No, casting out Message to Message is not safe.

Channel<out Message> basically means the Channel was restricted to only produce Message items, it can't consume them, for example because we don't know what types exactly it can consume. By casting it to Channel<Message> we allow consuming Message. The compiler has no way to verify this is correct neither at compile time or runtime, so it generates an unchecked cast warning.

If Channel type in your case is meant to only produce items, then it should be marked as out T at the declarations site. Then we can use it as simply Channel<Message>.

broot
  • 21,588
  • 3
  • 30
  • 35
  • I understand what it basically means and that casting it to `Channel` will allow it to consume messages but, shouldn't anything in `Channel` still technically be valid in `Channel` – Archmede Mar 13 '23 at 18:47
  • 3
    I'm not sure if I understand correctly. This is not about types of items "stored" in the channel currently. It is about what we can do with the channel object. By casting we add new functionality to it which it may not support. Similarly, casting `MutableList` to `MutableList` is not safe. Not because items inside it could be not animals (they have to be), but because after casting we can add new items that don't match the list type. – broot Mar 13 '23 at 19:22
1

Let me add to the previous answers for completeness. There are 2 levels to your question.

The "unchecked cast" warning is not about the types that you are trying to cast to/from. It's about the fact that there is no difference at runtime between Channel<X> and Channel<Y> due to type erasure. So a cast from one to the other will never fail at runtime even if it's wrong: it is not checked at runtime (hence the "unchecked"). This may lead to subtle bugs because you're polluting the heap.

This is different when casting non-generic types, like Any to String, or the raw part of generic types, like List<*> to MutableList<*>. Those would properly fail at runtime if the instance in question is not of the correct type.

Shouldn't this cast always be safe since Channel<out Message> can only contain objects of type Message or subclasses of type Message?

Actually no, it's not safe, as other answers have shown. The out in the declaration prevents you from sending elements into this channel (it's out-only, you can't put things in). This prevents you from inserting things that the channel cannot support. Casting would make the compiler allow that, which would be incorrect at runtime.

Joffrey
  • 32,348
  • 6
  • 68
  • 100