Advanced Types
Opaque types are erased to the underlying type at runtime — no boxing.
28.1 Union types (Scala 3)
def parse(s: String): Int | String =
s.toIntOption.getOrElse(s"invalid: $s")
parse("42") match
case n: Int => println(n + 1)
case err: String => println(err)
type Id = Int | String28.2 Intersection types
trait Resettable { def reset(): Unit }
trait Growable[A] { def add(a: A): Unit }
def clearAndFill(x: Resettable & Growable[String]): Unit =
x.reset()
x.add("fresh")28.3 Opaque types — zero-cost type safety
Opaque types are erased to the underlying type at runtime — no boxing.
object Units:
opaque type Meters = Double
object Meters:
def apply(d: Double): Meters = d
extension (m: Meters)
def +(o: Meters): Meters = m + o
def value: Double = m
opaque type Email = String
object Email:
def from(s: String): Option[Email] = Option.when(s.contains("@"))(s)
import Units.*
val d = Meters(3.0) + Meters(4.5)
// val x: Double = d // error: Meters is not Double outside Units28.4 Literal & singleton types
val one: 1 = 1
def onlyTrue(b: true): Unit = ()
val s: "hello" = "hello"
summon[ValueOf[42]].value // 4228.5 Structural types
Uses reflection (slow); Scala 3 also supports custom Selectable for typed records.
import scala.reflect.Selectable.reflectiveSelectable
def close(r: { def close(): Unit }): Unit = r.close()28.6 Path-dependent types
class Graph:
class Node(val id: Int)
def node(i: Int) = Node(i)
def connect(a: Node, b: Node) = ()
val g1 = Graph(); val g2 = Graph()
val n1 = g1.node(1)
g1.connect(n1, g1.node(2))
// g1.connect(n1, g2.node(3)) // error: g2.Node is not g1.Node28.7 Abstract type members vs type parameters
trait Container:
type Elem
def get: Elem
class IntBox extends Container:
type Elem = Int
def get = 4228.8 Explicit nulls (-Yexplicit-nulls)
val s: String = null // error
val t: String | Null = null
if t != null then t.length // flow typing narrows to String28.9 Phantom types & type-safe builders
sealed trait State; sealed trait Empty extends State; sealed trait Ready extends State
case class Request[S <: State](url: Option[String] = None):
def withUrl(u: String): Request[Ready] = Request(Some(u))
extension (r: Request[Ready]) def send(): String = s"GET ${r.url.get}"
Request[Empty]().withUrl("http://x").send()
// Request[Empty]().send() // compile error