Givens & Using Clauses

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, 2026-10-12) and every example was run with Scala CLI on JDK 26 on 2026-10-15 on Scala 3.10.0 unless a different version is named. given and using exist since Scala 3.0. The syntax introduced by SIP-64 in 3.6 (given [T: Ord] => Ord[List[T]], the colon instead of with, given c: () => T) works on the 3.9 LTS but not on the 3.3 LTS, which keeps the older with and (using ...) spellings; both are shown, and the examples marked as such were also run on 3.3.8 and 3.9.0. The overview of the whole feature family, with the Scala 2 mapping, is on Contextual Abstractions Overview. The examples use the trailhead scenario of this section: a toolkit that plans hikes on a network of mountain trails.

A given instance is a definition of a term that the compiler may supply when a context parameter of that type is required. A using clause declares such a context parameter. Together they let a call such as render(Km(4.5)) receive a Show[Km] argument without it being written, while keeping both the definition and the explicit passing visible in the syntax.

Given instances

A given is declared with given. There are several forms, which differ in how much of the definition is explicit.

Anonymous, named and alias givens

//> using scala 3.10.0

case class Km(value: Double)

trait Show[A]:
  def show(a: A): String

object Show:
  given Show[Km] = k => s"${k.value} km"                   // anonymous alias given (SAM lambda)
  given intShow: Show[Int]:                                // named given with a body
    def show(a: Int): String = s"#$a"
  given Show[Boolean] = ???                                // never used here: alias givens are lazy

def render[A](a: A)(using s: Show[A]): String = s.show(a)

@main def g1(): Unit =
  println(render(Km(4.5)))
  println(render(7))
  println(summon[Show[Km]].show(Km(1.0)))
  println(Show.intShow.show(3))
  println(Show.given_Show_Km.show(Km(2.0)))
4.5 km
#7
1.0 km
#3
2.0 km

The three forms in the example:

  • given Show[Km] = k => ... is an alias given: the right-hand side is an expression, and the given is an alias for its value. Here the expression is a lambda, which Scala converts to the single abstract method of Show. Alias givens are initialised lazily, on first use; that is why given Show[Boolean] = ??? does not fail the program (the Reference’s "Alias Givens" section says the same; a plain val plus an alias given is the way to get eager initialisation).

  • given intShow: Show[Int]: followed by an indented body defines an object (a class instance with members) that extends the type. It is the form to use when the instance has several members, or its own fields.

  • The name is optional. Without one the compiler derives a name from the type, so the given in the example is given_Show_Km. The last line of the example calls the member by that name to show it exists; do not write code that depends on a derived name, except in an import.

A given is a member of its enclosing object, class or package, so it can be defined at top level, in an object, inside a class, or locally in a block. Alias givens and givens with a body can appear in all of them.

Derived names and why to name a given

The Reference says that the compiler synthesizes a readable and reasonably concise name from the implemented type, and that the rules "do not guarantee absence of name conflicts between given instances of types that are 'too similar'". The name starts with given_, followed by the names of the type constructor and of its type arguments. These are the names the compiler generated in this experiment, read from the compiled class with reflection:

//> using scala 3.10.0
trait Show[A]
trait Tc[A, B]
object N:
  given Show[Int] = ???
  given Show[List[Int]] = ???
  given Show[Map[String, List[Int]]] = ???
  given [T] => Show[Set[T]] = ???
  given Tc[Int, String] = ???
  given Show[Array[Int]] = ???
  given Show[(Int, String)] = ???
@main def h8(): Unit =
  val names = classOf[N.type].getDeclaredMethods.map(_.getName).filter(_.startsWith("given")).sorted
  names.foreach(println)
given_Show_Array
given_Show_Array$lzyINIT1
given_Show_Int
given_Show_Int$lzyINIT1
given_Show_Int_String
given_Show_Int_String$lzyINIT1
given_Show_List
given_Show_List$lzyINIT1
given_Show_Map
given_Show_Map$lzyINIT1
given_Show_Set
given_Tc_Int_String
given_Tc_Int_String$lzyINIT1

The $lzyINIT1 methods are the lazy-initialisation helpers. Two observations from the list: the arguments of a nested type are dropped (Show[List[Int]] is given_Show_List, Show[Map[String, List[Int]]] is given_Show_Map), while the direct arguments are kept (Tc[Int, String] is given_Tc_Int_String). The first one is a trap, because two anonymous givens whose types differ only in a nested type argument get the same name:

//> using scala 3.10.0
trait Show[A]
object N:
  given Show[List[Int]] = ???
  given Show[List[String]] = ???
[error] ./h9.scala:5:9
[error] Conflicting definitions:
[error] final lazy given val given_Show_List: Show[List[Int]] in object N at line 4 and
[error] final lazy given val given_Show_List: Show[List[String]] in object N at line 5
[error]   given Show[List[String]] = ???
[error]         ^

The remedy is a name for at least one of them: given listOfInt: Show[List[Int]] = .... The same happens when a conditional given for List[A] and a given for List[Int] are both anonymous. Name a given when it is public API (the Reference advises named instances in published libraries, for binary compatibility), when you need to refer to it in an import by name, when its derived name would clash, or when it will be passed explicitly with using. Anonymous givens are best for instances that are always found implicitly.

Conditional givens

A given may itself require context parameters. In the 3.6 syntax the parameters come before =>, and a context bound can be written in the type parameter list:

//> using scala 3.10.0

trait Ord[T]:
  def compare(a: T, b: T): Int

given Ord[Int] = (a, b) => a.compare(b)
given [T: Ord] => Ord[List[T]]:                // conditional given with a context bound
  def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match
    case (Nil, Nil) => 0
    case (Nil, _)   => -1
    case (_, Nil)   => 1
    case (x :: xt, y :: yt) =>
      val c = summon[Ord[T]].compare(x, y)
      if c != 0 then c else compare(xt, yt)

given (o: Ord[Int]) => Ord[(Int, Int)] =        // conditional alias given with a named parameter
  (a, b) =>
    val c = o.compare(a._1, b._1)
    if c != 0 then c else o.compare(a._2, b._2)

@main def g2(): Unit =
  println(summon[Ord[List[Int]]].compare(List(1, 2), List(1, 3)))
  println(summon[Ord[List[List[Int]]]].compare(List(List(2)), List(List(1, 9))))
  println(summon[Ord[(Int, Int)]].compare((1, 2), (1, 2)))
  println(summon[Ord[List[Int]]].getClass.getName)
-1
1
0
g2$package$given_Ord_List

Reading the example: the Ord[List[T]] given exists only for element types that have an Ord. The search for Ord[List[List[Int]]] therefore runs the conditional given twice (for List[List[Int]] and then for List[Int]) and ends at the Ord[Int] given. Three notes on the syntax:

  • given [T: Ord] => ... is short for given [T] => (Ord[T]) => ...: the context bound becomes an anonymous context parameter. T: Ord as ord names it, and T: {Ord, Show} takes several (see Context Bounds & Deferred Givens).

  • The parameters are context parameters, not ordinary ones. In given (o: Ord[Int]) => Ord[(Int, Int)] the o is inferred. Writing (using o: Ord[Int]) in this position is an error on 3.10 ("`using` is already implied here").

  • The unnamed conditional given is named from its result type: the class name printed on the last line, g2$package$given_Ord_List, is the given for Ord[List[T]] (compiled inside the file’s top-level wrapper).

  • A conditional given creates a fresh instance for each reference, while an unconditional one is initialised on first use, as the Reference’s "Given Instance Initialization" section states.

The legacy with form

Before 3.6 the same two conditional givens were written with with for the body and (using ...) for the parameters:

//> using scala 3.3.8
trait Ord[T]:
  def compare(a: T, b: T): Int
given Ord[Int] with
  def compare(a: Int, b: Int) = a.compare(b)
given listOrd[T](using o: Ord[T]): Ord[List[T]] with
  def compare(xs: List[T], ys: List[T]) = xs.zip(ys).map(o.compare).find(_ != 0).getOrElse(xs.size.compare(ys.size))
@main def g16(): Unit =
  println(summon[Ord[List[Int]]].compare(List(1, 2), List(1, 3)))
-1

This compiled and printed the same result on 3.3.8 and, by default, on 3.10.0. With -source:future the compiler rejects it, with a message that shows the replacement:

[error] ./h1.scala:5:16
[error] Given member definitions starting with `with` are no longer supported; use `{...}` or `:` followed by newline instead
[error] given Ord[Int] with
[error]                ^
[error] ./h1.scala:7:7
[error] This old given syntax is no longer supported; use `=>` instead of `:`
[error] given [T](using Ord[T]): Ord[List[T]] with
[error]       ^

The legacy form is the correct choice for code that must compile on the 3.3 LTS; otherwise use the 3.6 form.

By-name givens

A given whose right-hand side is an alias is computed once. With an empty parameter list in the 3.6 syntax, it is computed again on every use:

//> using scala 3.10.0
import java.util.concurrent.atomic.AtomicInteger

class Clock(val id: Int)
val counter = AtomicInteger()

given lazyClock: Clock = Clock(counter.incrementAndGet())          // alias given: computed once, lazily
given byName: () => Clock = Clock(counter.incrementAndGet() * 100) // by-name given: computed at every use

def tick(using c: Clock) = c.id

@main def g14(): Unit =
  println(tick(using lazyClock))
  println(tick(using lazyClock))
  println(tick(using byName))
  println(tick(using byName))
1
1
200
300

The counter shows the difference: lazyClock returned 1 twice, byName evaluated its body again and returned 200, then 300. By-name givens fit values that must be fresh at each use, such as a clock or a request-scoped context. They are not the same as the by-name context parameters described below, which defer the search for an argument, not the value.

Using clauses

A using clause declares parameters that the compiler infers from the givens in scope:

//> using scala 3.10.0

trait Ord[T]:
  def compare(a: T, b: T): Int

given Ord[Int] = (a, b) => a.compare(b)

def max[T](xs: List[T])(using ord: Ord[T]): T =
  xs.reduce((a, b) => if ord.compare(a, b) >= 0 then a else b)

def maxAnon[T](xs: List[T])(using Ord[T]): T =        // anonymous using parameter
  xs.reduce((a, b) => if summon[Ord[T]].compare(a, b) >= 0 then a else b)

def planner(using name: String, level: Int): String = s"$name/$level"

given String = "trailhead"
given Int = 3

@main def g3(): Unit =
  println(max(List(3, 9, 4)))
  println(maxAnon(List(3, 9, 4)))
  println(max(List(3, 9, 4))(using (a, b) => b.compare(a)))   // explicit argument: reverses the order
  println(planner)
  println(planner(using "custom", 1))
  println(summon[Int])
9
9
3
trailhead/3
custom/1
3

Points to take from the example:

  • The parameter name is optional. (using ord: Ord[T]) names it, and (using Ord[T]) does not; an anonymous parameter is reachable with summon[Ord[T]].

  • A using clause can have several parameters, all of them given or all explicit: (using name: String, level: Int).

  • Explicit arguments are marked with using. max(xs)(using cmp) passes cmp instead of searching. This is the fix for the Scala 2 problem that f(arg) could not tell a regular argument from an implicit one. The example reverses the order for one call without defining a second given.

  • An explicit argument must still have the right type. Passing 3 to a Show[Km] parameter is rejected with "Found: (3 : Int), Required: Show[Km]", as for any argument.

  • A method can have several using clauses, and regular parameter lists between them. Each using clause is inferred independently, and (using n: Int = 2) can have a default. Vararg parameters are not supported in a using clause.

  • A using clause may be on a class constructor, on an extension method, and on a given definition (that is a conditional given). In a class, the parameter is visible to summon in the body; adding val (class Box(using val n: Int)) makes it a member, which can be imported (import box.n) to bring the given into another scope. Both were checked on 3.10.0.

summon

summon is a method in Predef with the signature def summon[T](using x: T): x.type. It asks the compiler for a given of type T in the current scope and returns it, typed precisely as the given that was found. It replaces implicitly, which still compiles. Typical uses: call a member of an anonymous parameter, check in a test that a given exists, and name a given for explicit passing. compiletime.summonInline does the same search at an inline call site.

//> using scala 3.10.0
case class Km(value: Double)
trait Show[A]:
  def show(a: A): String
given Show[Km] = k => s"${k.value} km"
class Report(using Show[Km]):                    // using clause on a class
  def line(k: Km) = summon[Show[Km]].show(k)
extension (k: Km)(using s: Show[Km])             // using clause on an extension
  def shown = s.show(k)
def twice[A](a: A)(using s: Show[A])(using n: Int = 2): String = List.fill(n)(s.show(a)).mkString(",")
@main def h5(): Unit =
  println(Report().line(Km(1)))
  println(Km(2).shown)
  println(twice(Km(3)))
  println(twice(Km(3))(using summon[Show[Km]])(using 3))
  println(summon[Show[Km]].show(Km(5)))
1.0 km
2.0 km
3.0 km,3.0 km
3.0 km,3.0 km,3.0 km
5.0 km

The third line of output shows a default value in a using clause: no Int given is in scope, so the default 2 was used. (The default is used only when the search fails.) Pass both using clauses explicitly, as the last call does, with one (using ...) per clause.

Context bounds are using clauses

A context bound is a using clause in disguise. These two methods are the same method:

//> using scala 3.10.0
def maxOf[T: Ordering](xs: List[T]): T = xs.max
def maxOf2[T](xs: List[T])(using Ordering[T]): T = xs.max
@main def h7(): Unit =
  println(maxOf(List(1, 5, 2)))
  println(maxOf2(List("a", "c", "b")))
5
c

By-name context parameters

A context parameter may be by-name: (using c: => Codec[Tree]). The compiler then passes the argument as a thunk, which allows a recursive structure of givens that would otherwise make the implicit search diverge. Consider a Codec for Tree, a recursive type, where Tree needs a codec for Option[Tree] and Option[Tree] needs a codec for Tree:

//> using scala 3.10.0
trait Codec[A]:
  def encode(a: A): String
case class Tree(value: Int, next: Option[Tree])
given treeCodec: (c: Codec[Option[Tree]]) => Codec[Tree] = t => s"Tree(${t.value}, ${c.encode(t.next)})"
given optCodec: (c: Codec[Tree]) => Codec[Option[Tree]] = _.fold("-")(c.encode)
@main def g13(): Unit =
  println(summon[Codec[Tree]].encode(Tree(1, None)))
[error] ./g13.scala:9:30
[error] No given instance of type Codec[Tree] was found for parameter x of method summon in object Predef.
[error] I found:
[error]
[error]     treeCodec(optCodec(/* missing */summon[Codec[Tree]]))
[error]
[error] But given instance treeCodec produces a diverging implicit search when trying to match type Codec[Tree].
[error]   println(summon[Codec[Tree]].encode(Tree(1, None)))
[error]                              ^

The search for Codec[Tree] needs Codec[Option[Tree]], which needs Codec[Tree] again, so the compiler stops it. Marking the context parameters by-name breaks the cycle; the search succeeds and the codecs refer to each other lazily:

//> using scala 3.10.0
trait Codec[A]:
  def encode(a: A): String

case class Tree(value: Int, next: Option[Tree])

given Codec[Int] = _.toString
given treeCodec: (c: => Codec[Option[Tree]]) => Codec[Tree] = t => s"Tree(${t.value}, ${c.encode(t.next)})"
given optCodec: (c: => Codec[Tree]) => Codec[Option[Tree]] = _.fold("-")(c.encode)

@main def g12(): Unit =
  println(summon[Codec[Tree]].encode(Tree(1, Some(Tree(2, None)))))
Tree(1, Tree(2, -))

This example was also run on 3.9.0. On the 3.3 LTS the same givens are written with with and (using c: => Codec[...]) (see the legacy form above); that variant was run on 3.3.8 and printed the same output. The Scala 2 equivalent is an implicit by-name parameter, (implicit c: => Codec[Tree]).

How a given is found

When the compiler needs an argument for a context parameter of type T, it looks in two places, in this order.

  1. The lexical scope. Givens that are defined, inherited or imported at the call site, searched from the innermost nesting level outwards. Definitions in an inner scope take precedence over outer ones.

  2. The implicit scope of T. The companion objects of T and of the classes and traits it is built from (its type arguments and its supertypes), and the prefixes of path-dependent types. This is why Ordering[Km] is found when Km has a companion with an Ordering, without any import.

If several candidates remain, the most specific one wins; if there is still more than one, the search is ambiguous. The next three examples show each rule.

//> using scala 3.10.0
// implicit scope: Km's companion is searched; a local given beats outer ones
case class Km(value: Double)
object Km:
  given Ordering[Km] = Ordering.by(_.value)           // found without any import

class Grid(val n: Int)
object Rules:
  given Grid = Grid(100)

def cells(using g: Grid) = g.n
def sortedKm(xs: List[Km])(using ord: Ordering[Km]) = xs.sorted

@main def h2(): Unit =
  println(sortedKm(List(Km(3), Km(1))))
  import Rules.given
  {
    given Grid = Grid(7)          // innermost scope wins over the import
    println(cells)
  }
  println(cells)
List(Km(1.0), Km(3.0))
7
100

The next example puts a given in the companion of the parameter type and another in an imported object. The imported one wins, because the lexical scope is searched before the implicit scope of the type:

//> using scala 3.10.0
class Grid(val n: Int)
object Grid:
  given Grid = Grid(42)
object Local:
  given Grid = Grid(9)
def cells(using g: Grid) = g.n
@main def h3(): Unit =
  println(cells)             // companion given: no import in scope
  import Local.given
  println(cells)             // imported given wins over the implicit scope
42
9

Specificity. With givens Show[Int], Show[List[A]] and Show[List[Int]] in scope, a request for Show[List[Int]] selects the one whose type is the most specific:

//> using scala 3.10.0
trait Show[A]:
  def show(a: A): String
given Show[Int] = i => s"int $i"
given [A] => Show[List[A]] = _ => "list"
given listInt: Show[List[Int]] = _ => "list of int"
def render[A](a: A)(using s: Show[A]) = s.show(a)
@main def h4(): Unit =
  println(render(1))
  println(render(List("x")))
  println(render(List(1)))
int 1
list
list of int

The listInt name is required: as the previous section showed, both Show[List[A]] and Show[List[Int]] would otherwise be called given_Show_List and the file would not compile ("Conflicting definitions").

Two imports in the same scope. In this run, importing the givens of two objects at the same level did not report an ambiguity: the import written last was used (import Others.given after import Defaults.given gave the Others value, and the reverse order gave the Defaults value). Do not rely on that: if two givens of the same type can reach a call site, import only one of them or pass the argument with using.

Importing givens

A wildcard import import A.* brings every member of A into scope except givens. Givens are imported by a separate selector, given:

//> using scala 3.10.0
object Defaults:
  val limit = 10
  given Int = 5
def showInt(using i: Int) = i
@main def g5b(): Unit =
  import Defaults.*
  println(showInt)
[error] ./g5b.scala:8:18
[error] No given instance of type Int was found for parameter i of method showInt
[error]
[error] Note: given instance given_Int in object Defaults was not considered because it was not imported with `import given`.
[error]   println(showInt)
[error]                  ^

The error message already names the fix. With import Defaults.given the program compiles and the given is used:

//> using scala 3.10.0
object Defaults:
  val limit = 10
  given Int = 5
  given String = "hi"
def showInt(using i: Int) = i
def f(using i: Int, s: String) = s"$i $s"
@main def g5(): Unit =
  import Defaults.{given, *}      // both the ordinary members and the givens
  println(limit)
  println(showInt)
  println(f)
10
5
5 hi

The rule exists for the reasons listed in the overview: it makes the set of imported givens visible in the import list, and prevents a wildcard import from bringing instances in silently. The selector forms are:

Import Imports

import A.*

everything except givens (and the Scala 2 implicit definitions, which * still imports)

import A.given

all givens of A, and no ordinary member

import A.given Show[?] or import A.{given Show[String]}

only the givens whose type conforms to the stated type

import A.{given, *}

both of the above

import A.given_Show_Km

a single given, by its (derived or explicit) name

Import by type. Because an anonymous given has an awkward name, the given T selector imports the givens of a type pattern. With a wildcard in the type argument position, import Names.given Show[?] brings in every Show given:

//> using scala 3.10.0
trait Show[A]:
  def show(a: A): String
object Names:
  given Show[Int] = _.toString
  given Show[String] = s => s"'$s'"
  given Show[Boolean] = _.toString.toUpperCase
def display[A](a: A)(using s: Show[A]) = s.show(a)
@main def g6(): Unit =
  import Names.given Show[?]
  println(display(1))
  println(display("x"))
  println(display(true))
1
'x'
TRUE

Importing only two of the three and then using the third reports the missing instance and suggests the import:

//> using scala 3.10.0
trait Show[A]:
  def show(a: A): String
object Names:
  given Show[Int] = _.toString
  given Show[String] = s => s"'$s'"
  given Show[Boolean] = _.toString.toUpperCase
def display[A](a: A)(using s: Show[A]) = s.show(a)
@main def g6b(): Unit =
  import Names.{given Show[String], given Show[Int]}
  println(display(1))
  println(display("x"))
  println(display(true))
[error] ./g6b.scala:13:24
[error] No given instance of type Show[Boolean] was found for parameter s of method display
[error]
[error] The following import might fix the problem:
[error]
[error]   import Names.given_Show_Boolean
[error]
[error]   println(display(true))
[error]                        ^

Two practical rules follow. Do not use the name given_Show_Boolean in application code unless an error message suggested it for an import: the derived name may change when the type changes. And put the instances a type class needs in its companion object (as in object Show above): the implicit scope then finds them for every client with no import at all. Import-by-type is for instances that live in separate "instance" objects, such as the Defaults and Rules objects above.

Errors

Missing given

The default message states which parameter of which method had no argument:

//> using scala 3.10.0
trait Show[A]:
  def show(a: A): String
case class Hut(beds: Int)
def render[A](a: A)(using s: Show[A]): String = s.show(a)
@main def g10(): Unit =
  println(render(Hut(4)))
[error] ./g10.scala:7:25
[error] No given instance of type Show[Hut] was found for parameter s of method render
[error]   println(render(Hut(4)))
[error]                         ^

The trait can carry a custom message with @implicitNotFound. Despite its name, the annotation is the Scala 3 mechanism too; type parameters are referred to as ${A}:

//> using scala 3.10.0
import scala.annotation.implicitNotFound

@implicitNotFound("No Show[${A}] in scope: write `given Show[${A}]` or import one.")
trait Show[A]:
  def show(a: A): String

def render[A](a: A)(using s: Show[A]): String = s.show(a)

case class Hut(beds: Int)
@main def g9(): Unit =
  println(render(Hut(4)))
[error] ./g9.scala:12:25
[error] No Show[Hut] in scope: write `given Show[Hut]` or import one.
[error]   println(render(Hut(4)))
[error]                         ^

Ambiguous givens

If two givens match with the same specificity at the same nesting level, the compiler reports both:

//> using scala 3.10.0
class Config(val name: String)
def run(using c: Config) = c.name
@main def g8(): Unit =
  given a: Config = Config("a")
  given b: Config = Config("b")
  println(run)
[error] ./g8.scala:7:14
[error] Ambiguous given instances: both given instance b and given instance a match type Config of parameter c of method run
[error]   println(run)
[error]              ^

Two anonymous givens of the same type in one object do not get that far, because their derived names clash:

//> using scala 3.10.0
class Config(val name: String)
object Both:
  given Config = Config("one")
  given Config = Config("two")
[error] ./g4d.scala:5:9
[error] Conflicting definitions:
[error] final lazy given val given_Config: Config in object Both at line 4 and
[error] final lazy given val given_Config: Config in object Both at line 5
[error]   given Config = Config("two")
[error]         ^

The fix is to remove one, narrow its scope, import only one, or pass the argument with (using a). When the design really needs several instances of one type, make them distinguishable with a wrapper type or an opaque type, or do not make them givens at all.

A given that requires, directly or after expansion, the type it provides makes the search diverge. The compiler detects it and prints the chain it built:

//> using scala 3.10.0
trait Tc[A]
given [A] => (Tc[List[A]]) => Tc[A] = ???
@main def g11(): Unit =
  summon[Tc[Int]]
[error] ./g11.scala:6:18
[error] No given instance of type Tc[Int] was found for parameter x of method summon in object Predef.
[error] I found:
[error]
[error]     given_Tc_A[Int](given_Tc_A[List[Int]](/* missing */summon[Tc[List[List[Int]]]]))
[error]
[error] But given instance given_Tc_A produces a diverging implicit search when trying to match type Tc[List[List[Int]]].
[error]   summon[Tc[Int]]
[error]                  ^

Each step asks for a larger type than the previous one, so the search could never end. By-name context parameters, shown above, handle the recursive but non-growing case.

Comparison

  • Scala 2: implicit val, implicit def, (implicit s: Show[A]) and implicitly do the same job; see the mapping table on the overview page. Differences that matter: anonymous givens get derived names, a Scala 2 implicit is imported by *, a given is not, and a given can be passed with (using x) where Scala 2 passed it in a normal argument list.

  • Java: there is no equivalent. A Comparator or a Show interface instance is passed explicitly, or injected by a framework.

  • Kotlin: extension functions and context parameters give some of the same effects, but there is no type-directed instance search comparable to Scala’s implicit scope, and no import selector that separates instances from other members.

  • Haskell, Rust: type class instances are global per type, so imports do not affect which instance is used. Scala’s lexical-scope resolution allows local overrides at the cost of the ambiguity rules above.

Where the books are outdated

  • Scala 2 books teach implicit for everything; Scala 3 uses given/using. Both compile on 3.10.0.

  • Books and posts written for 3.0 to 3.5 use given ... with and given [T](using Ord[T]): .... Those forms are correct on the 3.3 LTS and still compile on 3.10.0, but -source:future rejects them; the 3.6 forms are recommended for the 3.9 LTS and later.

  • The first edition of the Scala 3 Book’s pages on givens are accurate for the semantics (names, scope, imports) and use the with spelling in some examples.

  • "`import A._` brings implicits into scope" is a Scala 2 statement; in Scala 3 * does not import givens.

  • "The compiler stops at an ambiguity" is true for the same nesting level; the observation that two same-level given imports silently resolve to the last one is from the run above and is not documented behaviour.

The run results above are from 2026-10-15 on Scala 3.10.0, except where 3.3.8 or 3.9.0 are named.