Context Bounds & Deferred Givens
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Scala 3.10 (3.10.0, released 2026-10-12) and every example was compiled and run with Scala 3.10.0 through Scala CLI on JDK 26 (checked on 2026-10-15). Features newer than the 3.3 LTS line name their release; the named and aggregate context bounds and deferred givens (all 3.6) were also compiled with 3.9.0, and the 3.3.8 result is given where it matters.
A context bound is a shorthand for a context parameter that depends on a type parameter. Where a method says "I work for
any T, provided there is an Ordering[T]`", the bound writes that requirement next to `T instead of in a separate
using clause. Context bounds are what makes the type-class style readable, so this page builds on
Givens & Using Clauses (where instances are defined and
resolved) and leads into Type Classes and
Extension Methods. The bound T: Ordering only helps if a type can
be bounded, so it also touches Generics & Bounds.
The basic context bound
A bound T: Ordering on a type parameter T of a method or class adds a context parameter of type Ordering[T], called the
witness (older text says evidence) for the bound. The method below is written both ways to show that the two are the same
thing.
//> using scala 3.10.0
package trailhead
trait Show[T]:
extension (t: T) def show: String
given Show[Int] = n => s"#$n"
given Show[String] = s => s"'$s'"
// context bound
def longest[T: Ordering](xs: List[T]): T = xs.max
// the same method with an explicit using clause
def longestExplicit[T](xs: List[T])(using Ordering[T]): T = xs.max
// named bound (3.6)
def sorted[T: Ordering as ord](xs: List[T]): List[T] = xs.sortWith(ord.lt)
// two bounds, chained
def describe[T: Show: Ordering](xs: List[T]): String = xs.sorted.map(_.show).mkString(", ")
@main def run(): Unit =
println(longest(List(3, 9, 4)))
println(longestExplicit(List("b", "z", "a")))
println(sorted(List(5, 1, 3)))
println(describe(List(30, 10, 20)))
println(summon[Ordering[Int]].compare(1, 2))
9
z
List(1, 3, 5)
#10, #20, #30
-1
The compiler expands longest[T: Ordering](xs: List[T]) to longest[T](xs: List[T])(using Ordering[T]). The witness has a
compiler-synthesized name, so inside the body the instance is reached with summon[Ordering[T]], with the Ordering[T]
companion’s apply (Ordering[T] as a value, which the standard library defines), or implicitly, as xs.max does. When
a method has both a subtype bound and a context bound, the subtype bound comes first: def f[T <: B: C](x: T).
Source: Scala 3 Book: Context Bounds
Context bounds work on classes too, where the witness becomes a context parameter of the primary constructor. The same witness is then available to every method of the class.
//> using scala 3.10.0
package trailhead
class Box[T: Ordering](val items: List[T]):
def top: T = items.max
def ord: Ordering[T] = summon[Ordering[T]]
def ord2: Ordering[T] = summon
def f[T: Ordering](x: T, y: T) = Ordering[T].lt(x, y)
@main def run(): Unit =
val b = Box(List(1, 5, 2))
println(b.top)
println(b.ord eq b.ord2)
println(f(1, 2))
println(f(1, 2)(using Ordering[Int].reverse))
5
true
true
false
Source: Scala 3 Book: Context Bounds
The last line shows that a context bound is still an ordinary context parameter: an argument is passed explicitly with
(using ...), here the reversed ordering.
A bound can also constrain a type constructor, as in F[_]: Functor, so the same shorthand covers the higher-kinded type
classes of Type Lambdas & Higher Kinds.
//> using scala 3.10.0
package trailhead
trait Functor[F[_]]:
extension [A](fa: F[A]) def map[B](f: A => B): F[B]
given Functor[List] with
extension [A](fa: List[A]) def map[B](f: A => B): List[B] = fa.map(f)
def double[F[_]: Functor](fa: F[Int]): F[Int] = fa.map(_ * 2)
@main def run(): Unit = println(double(List(1, 2, 3)))
List(2, 4, 6)
Named context bounds
Because the witness is anonymous, code that needs the instance as a value must call summon. Scala 3.6 adds an as clause
after the type of a bound to give the witness a name. The name is analogous to an import rename, which also introduces a new
name for something that comes before it.
//> using scala 3.10.0
package trailhead
def pick[T: Ordering as ord](xs: List[T]): T = xs.reduce((a, b) => if ord.lt(a, b) then a else b)
@main def run(): Unit = println(pick(List(8, 3, 5)))
3
def pick[T: Ordering as ord](...) expands to def pick[T](...)(using ord: Ordering[T]). The generated parameter carries
exactly the name written after as. On Scala 3.3.8 the
same code does not compile, because as is not part of the syntax of that line:
Not found: type as
Not found: type ord
The reference notes a related language extension that would name an unnamed witness after its type parameter; it is experimental and this page does not use it.
Aggregate context bounds
Several bounds on one type parameter are written inside braces. Each element may have its own as clause.
//> using scala 3.10.0
package trailhead
trait Show[T]:
extension (t: T) def show: String
given Show[Int] = n => s"#$n"
given Show[String] = s => s"'$s'"
// aggregate bounds (3.6)
def render[T: {Ordering, Show}](xs: List[T]): String = xs.sorted.map(_.show).mkString(", ")
// named aggregate bounds
def renderMax[T: {Ordering as ord, Show as sh}](xs: List[T]): String =
sh.show(xs.max(using ord))
@main def run(): Unit =
println(render(List(3, 1, 2)))
println(renderMax(List("a", "c", "b")))
#1, #2, #3
'c'
The braces syntax is valid from Scala 3.6. It replaces the chained form [T: Show: Ordering] (a second colon for each extra
bound), which still compiles but is expected to be deprecated and removed over time. On Scala 3.3.8 the braces are a syntax
error:
Illegal start of declaration
Bounds on type members
A type member of a trait can carry a context bound as well, written type Element: Ord. That reads as "whoever fixes
Element must also supply an `Ord[Element]`". The bound is the abstract-type counterpart of a bound on a type parameter, and
it is the reason deferred givens exist (next sections).
//> using scala 3.10.0
package trailhead
trait Show[T]:
extension (t: T) def show: String
given Show[Int] = n => s"#$n"
trait Grid:
type Cell: Show
def cells: List[Cell]
def dump: String = cells.map(summon[Show[Cell]].show(_)).mkString(" ")
class IntGrid extends Grid:
type Cell = Int
def cells = List(1, 2, 3)
@main def run(): Unit = println(IntGrid().dump)
#1 #2 #3
Where the generated parameters go
The compiler places the witnesses by three rules, applied in this order:
-
If a bound is referred to by name in a later parameter clause, the bounds become a
usingclause immediately before that clause. A path-dependent parameter type needs this. -
Otherwise, if the last parameter clause is a
using(or Scala 2implicit) clause, the witnesses are merged in front of the parameters of that clause. -
Otherwise they form a new
usingclause at the end.
The first rule is new in 3.6; rules 2 and 3 match Scala 2, so binary compatibility with Scala 2 and earlier Scala 3 code is
kept. The example uses rule 1 so that the parameter in can have the type p.Input.
//> using scala 3.10.0
package trailhead
trait Parser[P]:
type Input
type Result
def parse(in: Input): Result
object DigitParser
given Parser[DigitParser.type] with
type Input = String
type Result = Int
def parse(in: String): Int = in.toInt
// rule 1: the named bound is used by the parameter type of the next clause
def run[P: Parser as p](in: p.Input): p.Result = p.parse(in)
def f[T: Ordering](x: T)(using n: Int): String = s"$x $n"
@main def go(): Unit =
println(run[DigitParser.type]("42"))
given Int = 7
println(f(1))
println(f(1)(using Ordering.Int, 3))
42
1 7
1 3
run expands to def run[P](using p: Parser[P])(in: p.Input): p.Result, with the using clause in front of the explicit
clause. f follows rule 2: the expansion is f[T](x: T)(using Ordering[T], n: Int), so the explicit arguments of the last
call are given in that order (the witness first). Putting 3 first is a compile error:
Found: (3 : Int)
Required: Ordering[Int]
Deferred givens
A trait cannot use a context bound on a type member directly the way a class does with a parameter, because the trait has no
constructor to receive the instance. Scala 3.6 adds a deferred given: a given whose right-hand side is
scala.compiletime.deferred. It can appear only in a trait. Each class that implements the trait supplies the
implementation, and if the class does not write one, the compiler synthesizes it by searching for a given of the required
type in the scope of the class (the class environment plus its parameters, but not its own members, which would cause
recursive resolution).
type Element: Ord in a trait is exactly shorthand for the deferred given form:
//> using scala 3.10.0
package trailhead
import scala.compiletime.deferred
trait Ord[T]:
def lt(a: T, b: T): Boolean
trait Collection:
type Elem
given Ord[Elem] = deferred // same as `type Elem: Ord`
def smallest(xs: List[Elem]): Elem =
xs.reduce((a, b) => if summon[Ord[Elem]].lt(a, b) then a else b)
given Ord[Int] = (a, b) => a < b
class Heights extends Collection:
type Elem = Int // given Ord[Int] found at the top level
@main def run(): Unit = println(Heights().smallest(List(8, 3, 5)))
3
The class Heights writes no implementation. The compiler searches for an Ord[Heights.this.Elem] outside the class,
finds the top-level given Ord[Int], and uses it. A class may also take the instance as a parameter, with the same
bound a trait cannot have:
//> using scala 3.10.0
package trailhead
trait Ord[T]:
def lt(a: T, b: T): Boolean
given Ord[Int] = (a, b) => a < b
trait Sorted:
type Element: Ord
def items: List[Element]
def isSorted: Boolean = items.zip(items.drop(1)).forall((a, b) => summon[Ord[Element]].lt(a, b))
// the class parameter `ord` implements the deferred given
class SortedSet[A: Ord as ord](val items: List[A]) extends Sorted:
type Element = A
// an explicit implementation counts as an override
class SortedStrings(val items: List[String]) extends Sorted:
type Element = String
override given Ord[Element] = (a, b) => a.length < b.length
@main def run(): Unit =
println(SortedSet(List(1, 2, 3)).isSorted)
println(SortedStrings(List("a", "bb", "ccc")).isSorted)
true
true
The compiler expands SortedSet to a class with (using ord: Ord[A]) and override given Ord[Element] = ord. An explicit
implementation is an override of a concrete definition, because the deferred given is concrete as far as the language is
concerned, so it needs the override modifier. The deferred given reads like a compiler-supported macro whose
implementation also affects every subclass.
Common mistakes
If the compiler finds no instance, the implementing class is rejected at its declaration, not at the use site:
//> using scala 3.10.0
package trailhead
trait Ord[T]
trait Sorted:
type Element: Ord
class NoInstance extends Sorted:
type Element = String
No given instance of type trailhead.Ord[NoInstance.this.Element] was found for inferring the implementation of the deferred given instance given_Ord_Element in trait Sorted
The synthesized member is named from the written type, given_Ord_Element. An explicit implementation therefore has to be
written as override given Ord[Element] (or Ord[Elem], whatever the trait wrote). Writing it as override given Ord[String]
produces a differently named member that does not override the deferred one, and the same error as above is reported even
though an Ord[String] is in scope.
Abstract givens
Before 3.6 the way to require a given from a subclass was an abstract given: a named given without a right-hand side, as in
given ord: Ord[T], which every class then had to define. Since 3.6 the reference calls abstract givens redundant and
recommends migrating to deferred givens, which are implemented automatically. Abstract givens still compile on 3.10.
Context bounds in Scala 2
Scala 2 has the same bound with the same syntax, def maximum[T: Ordering](xs: List[T]), and it expands to an implicit
parameter list. The braces form, the as name and the deferred given are Scala 3.6 additions. In Scala 2 the evidence has no
usable name, so code that needs it writes implicitly[Ordering[T]], or declares the implicit parameter by hand:
def pick[T](xs: List[T])(implicit ord: Ordering[T]): T =
xs.reduce((a, b) => if ord.lt(a, b) then a else b)
The migration rule is a difference worth knowing: in Scala 3.0 to 3.5 a context bound maps to an old-style
implicit parameter, for which an argument can be passed either with (using ...) or as a plain argument. From 3.6 on it maps
to a real context parameter, and the plain argument no longer compiles. Compiled with 3.3.8, f(1, 2)(Ordering.Int) works.
With 3.10.0 it is rejected:
method f in package trailhead does not take more parameters
The fix is f(1, 2)(using Ordering.Int). The compiler can rewrite such calls with the 3.4-migration source setting and
-rewrite.
Java and Kotlin
Java has no context parameters. Its closest feature is a bound on the type parameter, <T extends Comparable<T>>, which
requires the type itself to implement the interface. A context bound does not touch the type: Ordering[Int] works though
Int has no ordering method, and a second instance (the reverse ordering) can be passed for the same type. The Java version
of that flexibility is an explicit Comparator<T> argument, which has to be passed at every call. Kotlin is similar: the
upper bound T : Comparable<T> fixes the instance to the type, and a Comparator<T> is passed by hand otherwise. In Scala
the compiler passes the argument, and the same call still lets a caller override the choice with using.
Scala 3.9 LTS versus 3.10
-
Context bounds,
asnames, braces and deferred givens all arrived in 3.6 and behave the same on 3.9 and 3.10. The named, aggregate and deferred examples above gave identical output with 3.9.0. -
On the 3.3 LTS line only the basic bound
T: Orderingand the chainedT: A: Bforms exist;as, braces andtype Element: Ordin a trait do not compile (checked with 3.3.8).