Union types in Scala 3

I used the term pseudo-union precisely because it is user-level; not added to the core type system. This causes some limitations in terms of usability. The most typical example is that it will report a warning that the following cases in the match are not possible:

val x: Int | String = ???
x match {
  case x: Int => println("int")
  case x: String => println("string")
}

To convince the compiler to accept the code, you need a type ascription to Any:

(x: Any) match {
  ...
}

Even in that case, you do not get exhaustivity checking (obviously).

Other than some limitations like that, | is truly an unboxed union type. A | B accepts values that are either A or B (or both) but no other value. At run-time those values are not wrapped (this can be observed by casting back to A or B if you know which one it is, or type testing with .isInstanceOf[A], both of which will work).

As a consequence, it is not a discriminated union. If a value x is both an A and a B, you cannot tell from which branch it came. In fact, | is commutative: you can assign an A | B to a B | A and conversely.

6 Likes