Scala 3 New Features Cheat Sheet
Covers Scala 3's new syntax: significant indentation, enums, given/using, extension methods, union types, and opaque type aliases.
Optional Braces & Enums
Scala 3 supports significant-indentation syntax and a first-class `enum` construct replacing sealed trait boilerplate.
// significant indentation (braces optional)def greet(name: String): String = if name.isEmpty then "Hello, stranger" else s"Hello, $name"enum Color: case Red, Green, Blueenum Shape: case Circle(radius: Double) case Rectangle(width: Double, height: Double)def area(s: Shape): Double = s match case Shape.Circle(r) => math.Pi * r * r case Shape.Rectangle(w, h) => w * h
`given`/`using` (Replaces `implicit`)
Scala 3 splits implicits into clearer, purpose-specific constructs.
trait Show[T]: def show(t: T): Stringgiven Show[Int] with def show(t: Int): String = t.toStringgiven Show[String] with def show(t: String): String = tdef display[T](value: T)(using s: Show[T]): String = s.show(value)display(42) // uses the Show[Int] givendisplay("hello") // uses the Show[String] given// context bound sugardef display2[T: Show](value: T): String = summon[Show[T]].show(value)
Extension Methods & Union Types
Add methods to existing types without inheritance, and express "one of several types" directly.
extension (s: String) def shout: String = s.toUpperCase + "!""hi".shout // => "HI!"extension [T](xs: List[T]) def secondOption: Option[T] = xs.drop(1).headOption// Union typesdef process(input: Int | String): String = input match case i: Int => s"Number: $i" case s: String => s"Text: $s"process(5) // "Number: 5"process("abc") // "Text: abc"
Opaque Type Aliases
Zero-cost type-safe wrappers with no boxing overhead, replacing value classes for many cases.
opaque type UserId = Longobject UserId: def apply(id: Long): UserId = id extension (id: UserId) def value: Long = idopaque type Meters = Doubleobject Meters: def apply(d: Double): Meters = d extension (m: Meters) def +(other: Meters): Meters = m + other def toDouble: Double = mval u: UserId = UserId(42)// u + 1 // compile error — UserId is not a Long outside its own scope
Scala 2 → Scala 3 Syntax Map
Quick lookup when migrating or reading unfamiliar code.
- implicit val/def (typeclass instance)- now `given ... with` or `given name: Type = ...`
- implicit parameter- now `using` parameter clause
- implicit conversion (def, 1 arg)- now `given Conversion[A, B] with`
- sealed trait + case objects/classes- now often a plain `enum`
- trait + implicit class extension- now `extension (x: T) def foo = ...`
- Either[A, B] as sum boundary- often replaced by native `A | B` union types
- Curly-brace blocks- optional; indentation-based syntax is now first-class
Match Types
Match types compute a type from another type's shape, resolved at compile time like a type-level pattern match.
type Elem[X] = X match case String => Char case Array[t] => t case Iterable[t] => tsummon[Elem[String] =:= Char] // compilessummon[Elem[Array[Int]] =:= Int] // compilesdef firstElem[T](xs: T)(using ev: T <:< Iterable[?]): Elem[T] = ev(xs).head.asInstanceOf[Elem[T]]firstElem(List(1, 2, 3)) // => 1, typed as Elem[List[Int]] = Int
`inline` & Compile-Time Metaprogramming
`inline` forces expansion at the call site, enabling zero-cost abstractions and compile-time checks via the `scala.compiletime` package.
import scala.compiletime.{error, erasedValue, summonInline}inline def assertPositive(inline n: Int): Unit = inline if n < 0 then error("n must be positive") else ()assertPositive(5) // fine// assertPositive(-1) // compile error: "n must be positive"// recursive inline over a Tuple's element typesinline def summonAll[T <: Tuple]: List[Any] = inline erasedValue[T] match case _: EmptyTuple => Nil case _: (t *: ts) => summonInline[t] :: summonAll[ts]
Typeclass Derivation with `derives`
The `derives` clause auto-generates typeclass instances using compiler-provided `Mirror` metadata, replacing hand-written Shapeless-style derivation.
import scala.deriving.Mirrortrait Show[T]: def show(t: T): Stringobject Show: inline def derived[T](using m: Mirror.Of[T]): Show[T] = new Show[T]: def show(t: T): String = inline m match case p: Mirror.ProductOf[T] => t.asInstanceOf[Product].productIterator.mkString("(", ",", ")") case s: Mirror.SumOf[T] => t.toStringcase class Point(x: Int, y: Int) derives Showsummon[Show[Point]].show(Point(1, 2)) // => "(1,2)"
Context Functions & Polymorphic Function Types
`?=>` builds a function whose parameter is supplied implicitly at the call site; `[T] => ...` writes a generic function as a value rather than a method.
import scala.concurrent.ExecutionContexttype Executable[T] = ExecutionContext ?=> Tdef fetchUser(id: Int): Executable[String] = summon[ExecutionContext] // available implicitly inside the context function s"user-$id"given ExecutionContext = ExecutionContext.globalval result: String = fetchUser(1) // ctx applied automatically at use site// Polymorphic function value (not just a method)val reverseAny: [T] => List[T] => List[T] = [T] => (xs: List[T]) => xs.reversereverseAny(List(1, 2, 3)) // => List(3, 2, 1)
Scala 3 Advanced-Feature Glossary
Terms that show up once you move past basic given/using/enum syntax.
- match type- a type computed from another type's structural shape, resolved at compile time
- inline- forces a def/val to be expanded at the call site instead of compiled as a normal call
- transparent inline- an inline def whose return type is refined to the most specific type possible after expansion
- derives clause- asks the compiler to synthesize a typeclass instance via Mirror-based derivation
- Mirror- compiler-generated metadata describing a type's shape (product/sum, field types, labels)
- context function (?=>)- a function type whose argument is filled in implicitly, not passed explicitly
- polymorphic function type- a generic function written as a first-class value: `[T] => T => T`
- compiletime.error- aborts compilation with a custom message from inside an inline branch
Migrate incrementally with the `-source:3.0-migration` compiler flag (or scalafix rules) rather than hand-rewriting — most Scala 2 implicit-based typeclass code maps mechanically onto given/using and the compiler will point out exactly where it doesn't.