Libraries use these for zero-overhead abstractions, derivation and compile-time checks.
Scala 3 has a principled metaprogramming system built in layers.
`inline` is the first layer. An inline def is guaranteed to be inlined at each call site. Combined with inline parameters, inline if and inline match, the branches are resolved during compilation, so code specialised to constants costs nothing at runtime. transparent inline methods may even return a more specific type than declared.
scala.compiletime adds tools usable inside inline code: error("msg") emits a custom compile error, constValue[T] turns a literal type into a value, erasedValue[T] lets you match on types, and summonInline/summonFrom look up givens at expansion time. This is enough for a lot of type-class derivation with scala.deriving.Mirror.
Macros are the second layer. An inline method calls ${ impl('x) }, and impl is a regular method running in the compiler that receives typed code as Expr[T] and returns new code built with quotes '{ ... } and splices ${ ... }. Macros are type-checked, hygienic and, unlike Scala 2 macros, part of the stable language. The reflection API (quotes.reflect) gives full access to trees when quotes aren't enough.
Rules: a macro implementation must be defined in a different file (compiled earlier) than its call sites, and macros can't introduce new definitions visible to the user's code. Use inline first and reach for macros only when you need to inspect or generate code.
Example
import scala.compiletime.{error, constValue, erasedValue}
inline def power(x: Double, inline n: Int): Double =
inline if n == 0 then 1.0
else inline if n % 2 == 1 then x * power(x, n - 1)
else power(x * x, n / 2) // unrolled at compile time
inline def checkedPort(inline p: Int): Int =
inline if p < 1 || p > 65535 then error("port out of range") else p
inline def typeName[T]: String = inline erasedValue[T] match
case _: Int => "Int"
case _: String => "String"
case _ => "other"
type Size = 3
@main def meta(): Unit =
println(power(2.0, 10)) // 1024.0
println(checkedPort(8080)) // 8080
// checkedPort(70000) // compile error: port out of range
println(typeName[String]) // String
println(constValue[Size] * 2) // 6
// Macro sketch (impl must live in another file):
// inline def show(inline x: Any): String = ${ showImpl('x) }
// def showImpl(x: Expr[Any])(using Quotes): Expr[String] = Expr(x.show)Remember
inlineguarantees inlining;inline if/matchresolve at compile timescala.compiletimegives error, constValue and erasedValue- Macros use quotes
'{}and splices${}over typedExprtrees - Macro implementations must be compiled before their call sites
Watch out for
- Reaching for macros when an inline method with compiletime operations would do.