Skip to content
AgileLogics
5 min read

Inline and Metaprogramming

Prerequisites: Type classes

Scala 3 can run code at compile time, producing faster or safer programs.

inline

An inline method is expanded at each call site. Combined with inline if and constant arguments, branches are resolved during compilation:

inline val debugEnabled = false

inline def debug(inline msg: String): Unit =
  inline if debugEnabled then println(s"[debug] $msg")

def expensive(): String =
  println("computing...")
  "details"

@main def inlining(): Unit =
  debug(s"state: ${expensive()}")
  println("done")
done

Because debugEnabled is false, the whole debug call — including expensive() — is removed from the compiled code.

Compile-time checks

import scala.compiletime.{error, constValue}

inline def percentage(inline value: Int): Int =
  inline if value < 0 || value > 100 then error("percentage must be between 0 and 100")
  else value

@main def checks(): Unit =
  println(percentage(42))
  println(constValue[3] + 1)
42
4
import scala.compiletime.error

inline def percentage(inline value: Int): Int =
  inline if value < 0 || value > 100 then error("percentage must be between 0 and 100")
  else value

@main def badCheck(): Unit =
  println(percentage(150))
-- Error: Main.scala:8:20 ------------------------------------------------------
8 |  println(percentage(150))
  |          ^^^^^^^^^^^^^^^
  |          percentage must be between 0 and 100
1 error found

The invalid literal is rejected when compiling, with your message.

Deriving with Mirror

Mirror gives compile-time access to a case class's structure — the foundation of derives:

import scala.deriving.Mirror
import scala.compiletime.constValueTuple

inline def fieldNames[A](using m: Mirror.ProductOf[A]): List[String] =
  constValueTuple[m.MirroredElemLabels].toList.map(_.toString)

case class User(id: Long, name: String, email: String)

@main def mirrors(): Unit =
  println(fieldNames[User])
List(id, name, email)

Macros

For full control, Scala 3 macros use quotes ('{ ... }) and splices (${ ... }) to generate code from typed syntax trees. They're powerful but complex — reach for inline, Mirror, and derives first. Libraries use macros for things like compile-time SQL checking and JSON codecs.