Scala 3 given/using: implicit parameters, renamed and rebuilt
The given / using rework in Scala 3 isn't just a syntax swap. It's a genuine redesign of how implicit context works in the language — driven by a decade of Scala 2 pain around implicit scope, derivation, and accidental ambiguity. I've been using it on a real codebase for a few months; here's what holds up and what I had to relearn.
The core rename
Scala 2:
def greet(name: String)(implicit greeting: Greeting): String =
s"${greeting.text}, $name!"
implicit val english: Greeting = Greeting("Hello")
Scala 3:
def greet(name: String)(using greeting: Greeting): String =
s"${greeting.text}, $name!"
given Greeting = Greeting("Hello")
Two things changed. First, using at the call site is distinct from implicit, so you can read a signature and immediately tell "this parameter is passed implicitly" without the word being overloaded with six other meanings. Second, given replaces implicit val/implicit def with a dedicated keyword whose only job is to provide contextual values.
Why this matters
In Scala 2, the keyword implicit did four different jobs: declaring implicit parameters, declaring implicit conversions, declaring typeclass instances, and enabling extension methods. Scala 3 gave each of these its own syntax:
usingfor implicit parameters at use sites.givenfor providing values.extensionfor adding methods to a type.Conversion[A, B]as an explicit typeclass for coercions — no more mysteriousimplicit defthat could fire anywhere.
The split makes code immediately more readable. You can look at a file and see exactly what is being added to the scope and in what role.
Anonymous givens
One pattern I use a lot is anonymous givens — no name required:
given Greeting = Greeting("Hello")
given Codec[User] = Codec.derived[User]
In Scala 2, you had to come up with names for these (implicit val defaultGreeting, implicit val userCodec), which led to a kind of fake-namespace cargo cult. Anonymous givens admit that most typeclass instances are uniquely identified by their type, and that picking a name for them is usually fiction.
Context bounds, upgraded
Context bounds in Scala 3 work the same as in 2 at the syntactic level — def f[A: Ord] still means "take an implicit Ord[A]". But they now compose with using parameters naturally, and you can reference the given via summon[Ord[A]] instead of the old implicitly trick. Small change, but summon reads better and makes type inference cleaner in more edge cases.
The things I still trip on
Importing givens. Unlike Scala 2, given values are not imported by a regular import foo._. You need either import foo.given or the specific syntax import foo.{given Greeting}. This tripped me up for weeks — perfectly valid givens in scope, refusing to resolve, because I was writing import statements the old way. Once I got it, it started to feel like a feature: givens don't leak as accidentally as implicits did.
Derivation ergonomics. Typeclass derivation in Scala 3 is dramatically better than the Magnolia dance we did in Scala 2, but there's still a learning curve around Mirror, inline given, and when to use derives on the class versus declaring the derivation separately. Worth learning — it unlocks things that were painful to express before — but it's not trivial.
Verdict
A clear win. The rename alone would have been useful; the semantic cleanup on top of it makes Scala 3 feel like a language where the implicit machinery was designed with the pitfalls of Scala 2 specifically in mind. Migration has small frictions, but nothing that wasn't worth the payoff after a few weeks.