Given and Using in Scala 3: Implicits Reimagined for Clarity
The previous guide covered classic implicits and their central criticism: one overloaded keyword (implicit) doing four unrelated jobs โ parameters, conversions, classes, and values โ with no syntactic distinction between them at the point of use. Scala 3 doesnโt remove this capability; it splits it into purpose-specific keywords, each doing exactly one job, with syntax that makes โwhat kind of implicit thing is thisโ visible at a glance instead of requiring context to infer.
given โ Declaring an Implicit Value
// Scala 2 styleimplicit val defaultTimeout: Int = 30
// Scala 3 stylegiven defaultTimeout: Int = 30// A given instance for a trait โ the type class pattern, Scala 3 styletrait Show[T]: def show(value: T): String
given intShow: Show[Int] with def show(value: Int): String = s"Int: $value"given replaces implicit val/implicit def for declaring a value the compiler can supply automatically. The name (intShow above) is often omittable entirely in Scala 3 โ given Show[Int] with ... works without a name at all, since given declarations are looked up by type, not by name, making the name mostly documentation rather than something callers ever reference directly.
// Anonymous given โ genuinely common in Scala 3, since the name is rarely used directlygiven Show[Int] with def show(value: Int): String = s"Int: $value"
given Show[String] with def show(value: String): String = s"String: $value"using โ Requesting an Implicit Parameter
// Scala 2 styledef greet(name: String)(implicit greeting: String): String = s"$greeting, $name!"
// Scala 3 styledef greet(name: String)(using greeting: String): String = s"$greeting, $name!"
given defaultGreeting: String = "Hello"greet("Alice") // "Hello, Alice!"using replaces the implicit keyword specifically on the parameter list side โ a function requiring a contextual value now says so with using instead of the overloaded implicit. This one-to-one mapping (given produces values, using consumes them) is the core clarity improvement: reading a function signature, using unambiguously means โthis needs a contextual value supplied automatically,โ a job implicit used to share with three other unrelated meanings.
// Anonymous using parameters โ when you don't need to reference the parameter by namedef display[T](value: T)(using Show[T]): String = summon[Show[T]].show(value)summon[T] is Scala 3โs replacement for implicitly[T] from classic implicits โ retrieving the current implicit/given value of type T by name, used here since the using parameter itself has no name to reference directly.
Context Bounds Still Work Identically
def maxOf[T: Ordering](list: List[T]): T = list.maxContext bound syntax (T: Ordering) is unchanged between Scala 2 and Scala 3 โ it expands to a using clause under the hood in Scala 3 ((using ord: Ordering[T])), exactly mirroring how it expanded to an implicit clause in Scala 2. This is genuinely one of the more reassuring facts about the migration: the most common day-to-day pattern for โrequires a type class instanceโ didnโt need to change at all.
given Instances Are Anonymous by Default โ And Thatโs Deliberate
This is a genuine, intentional design shift from Scala 2: classic implicits were technically also found by type, but the implicit val name: Type syntax made it look like the name mattered, leading to unnecessary bikeshedding over naming and occasional confusion about whether name collisions caused problems (they generally didnโt, since resolution was always type-based). given instances make this explicit โ the name is optional specifically because it was never the actual resolution mechanism, only ever documentation.
Extension Methods โ Replacing Implicit Classes
// Scala 2 style โ implicit classimplicit class StringOps(s: String) { def shout: String = s.toUpperCase + "!"}
// Scala 3 style โ extensionextension (s: String) def shout: String = s.toUpperCase + "!"
"hello".shout // "HELLO!" โ identical usage, clearer declarationextension is a dedicated keyword purpose-built for exactly the โadd a method to a type you donโt ownโ use case that implicit classes were previously repurposed for โ no implicit resolution machinery involved at all, just a direct, named language feature for extension methods. This is a strict readability upgrade with no behavior change: extension methods work identically to their implicit-class equivalents from the callerโs perspective.
// Multiple extension methods on the same type, grouped togetherextension (s: String) def shout: String = s.toUpperCase + "!" def isPalindrome: Boolean = s == s.reverse def wordCount: Int = s.split("\\s+").length// Generic extension methodsextension [T](list: List[T]) def secondOption: Option[T] = list.drop(1).headOption
List(1, 2, 3).secondOption // Some(2)given Imports โ Explicit About What Youโre Bringing Into Scope
// Scala 2 โ a wildcard import brings in everything, implicits included, indistinguishablyimport mypackage.config._
// Scala 3 โ explicit syntax for importing ONLY given instancesimport mypackage.config.givenScala 3 adds import ... .given specifically to make it visible at the import site that youโre pulling in contextual/given values, separate from ordinary imports of classes and functions โ this is a direct response to another classic-implicits complaint: a wildcard import in Scala 2 silently brought in implicits alongside everything else, with no way to tell from the import line alone whether implicit behavior was being introduced.
import mypackage.config.given Show[Int] // even more specific โ import only a given of this exact typeConversions as a Dedicated given Type
case class Celsius(value: Double)case class Fahrenheit(value: Double)
given Conversion[Celsius, Fahrenheit] with def apply(c: Celsius): Fahrenheit = Fahrenheit(c.value * 9 / 5 + 32)
def printTemp(f: Fahrenheit): Unit = println(s"${f.value}ยฐF")
import scala.language.implicitConversionsprintTemp(Celsius(20)) // implicitly converted via the given Conversion instance โ prints "68.0ยฐF"Scala 3 makes implicit conversions a specific, named given Conversion[A, B] instance rather than an arbitrary implicit def with one argument โ this is more restrictive by design (a conversion has to explicitly declare itself as one, using the dedicated Conversion trait) and still requires the same import scala.language.implicitConversions opt-in as Scala 2, reflecting that this remains the most cautionary of the given/using-related features even in its cleaned-up form.
Deriving given Instances From Other given Instances
trait Show[T]: def show(value: T): String
given Show[Int] with def show(value: Int): String = value.toString
given [T](using elemShow: Show[T]): Show[List[T]] with def show(value: List[T]): String = value.map(elemShow.show).mkString("[", ", ", "]")
def display[T](value: T)(using s: Show[T]): String = s.show(value)
display(List(1, 2, 3)) // "[1, 2, 3]" โ Show[List[Int]] derived automatically from Show[Int]This is a genuinely powerful pattern: a given can itself take a using parameter, letting the compiler chain together instances automatically โ a Show[List[Int]] doesnโt need to be written by hand, because the compiler can derive it from an existing Show[Int] plus this generic given rule for any List[T] where a Show[T] exists. This chaining is exactly how real-world JSON libraries (circe, mentioned in the case classes guide) automatically derive encoders for List[User], Option[Order], or deeply nested structures from a handful of base instances, without manually writing an encoder for every possible combination.
Migrating Between the Two Styles
| Scala 2 (classic implicits) | Scala 3 (given/using) |
|---|---|
implicit val x: Int = 5 | given x: Int = 5 |
def f(implicit x: Int) | def f(using x: Int) |
implicit def conv(x: A): B = ... | given Conversion[A, B] = ... (conversions are now their own explicit given type) |
implicit class Ops(x: X) { def m = ... } | extension (x: X) def m = ... |
implicitly[T] | summon[T] |
T: TypeClass (context bound) | T: TypeClass โ unchanged |
Scala 3โs compiler accepts classic implicit syntax in a compatibility mode for migration purposes, and existing Scala 2 libraries (including much of the Spark and Akka ecosystem) continue to work unmodified โ this table is primarily useful for reading older code fluently and for writing new Scala 3 code idiomatically, not because every existing codebase needs an immediate rewrite.
Why This Redesign Happened
The underlying capability โ contextual, automatically-supplied values and extensible types โ didnโt change between Scala 2 and Scala 3; what changed is that four genuinely distinct concerns (declaring a contextual value, requesting one, converting between types, and extending an existing type with new methods) no longer share one overloaded keyword. This matters practically: a developer reading given/using/extension code can identify which of these four things is happening from the keyword alone, without needing to trace through how implicit happened to be used in that particular spot โ a direct, measurable readability improvement for a feature that was Scalaโs single most commonly cited barrier to learning the language, as referenced in the first guide in this series.
Common Pitfalls
| Symptom | Likely cause |
|---|---|
| โno given instance foundโ | The same as a Scala 2 โcould not find implicit valueโ โ no matching given is in scope; check imports, including whether you need import ....given specifically |
| Ambiguous given instances | Two given values of the same type are both in scope at equal priority โ remove or scope one more narrowly |
A derived given (using another given via a using parameter) doesnโt resolve | Check that the base given instance it depends on is genuinely in scope where the derived one is defined, not just where itโs ultimately used |
Mixing implicit and given syntax in the same signature causes a compile error | using-clause parameters and old-style implicit parameter lists canโt be mixed within the same parameter list โ pick one style per method signature |
extension method not found | The extension block itself needs to be imported/in scope, exactly like an implicit class needed to be โ extension methods are still resolved via normal scoping rules, not truly โglobalโ |
Frequently Asked Questions
Do I need to rewrite existing Scala 2 implicit code to use given/using? No โ Scala 3 supports both, and a large amount of production code (and most existing libraries) still uses classic implicit syntax. New code, and code youโre actively refactoring, is the place to prefer given/using.
Is a given instance required to have a name? No โ names are optional specifically because resolution happens by type, not by name. A name is useful mainly for documentation or on the rare occasion you need to reference the specific instance directly (with summon or an explicit reference), rather than relying on automatic resolution.
Can I mix given/using and classic implicit in the same file? Generally yes, for interoperability during migration, though mixing styles within the same logical section of code is more of a readability choice than a technical requirement โ most teams standardize on one style per file or module for consistency.
Is extension strictly better than implicit class in every case? For the common โadd methods to an existing typeโ use case, yes โ itโs clearer with identical capability. implicit class remains necessary only when working in a codebase still targeting Scala 2 syntax specifically.
Does given/using change how implicit resolution actually searches for a match? The underlying search behavior (by type, checking local scope, companion objects, and so on) is largely preserved conceptually, though Scala 3โs specification refines and tightens some of the more ambiguous edge cases from Scala 2โs resolution rules โ the practical day-to-day experience is meant to feel like the same mechanism, described more clearly.
Whatโs Next
given/using is Scala 3โs clarity-focused redesign of the exact mechanism implicits provided โ and itโs the direct foundation for the type class pattern. Next: Type Classes in Scala, covering ad hoc polymorphism built entirely from the given/using (or classic implicit) machinery covered in this guide and the last.