kitlangton / zio-magic

Construct ZLayers automagically (w/ helpful compile-time errors)

Version Matrix

🪄 zio-magic

Release Artifacts Snapshot Artifacts

ANNOUNCEMENT: This library will officially be a first-class feature of ZIO 2.0. I'll link to the relevant PR once that is public, as there are some changes and improvements in there (including Scala 3 support) that haven't been backported yet. But I'll support this library until at least ZIO 2.0 is released officially! 😊

Construct ZLayers automagically, with friendly compile-time hints!

// build.sbt
libraryDependencies += "io.github.kitlangton" %% "zio-magic" % "0.3.2"

What's all this then?

// Given a dependency graph (Cake needs Chocolate & Flour, which in turn need Spoon)*
//          Cake
//          /   \
//   Chocolate   Flour
//       |         |
//     Spoon     Spoon
// *Not an actual recipe.

def run(args: List[String]): URIO[ZEnv, ExitCode] = {
  // An effect requiring Cake and Console. Yum!
  val program: URIO[Console with Cake, Unit] =
    Cake.isDelicious.flatMap { bool => console.putStrLn(s"Cake is delicious: $bool") }

  // The old way
  val manually: ULayer[Cake with Console] =
    (( >>> ++ ( >>> >>> ++

  // The magical way (The order doesn't matter)
  val magically: UIO[Unit] =


And if you leave something off, a compile time clue!

val magically: UIO[Unit] =
    //, <-- Oops,,
   ZLayer Wiring Error

>  provide zio.magic.Example.Flour.Service
>      for

Versus leaving out a dependency when manually constructing your layer...

 val manually: ULayer[Cake with Console] =
   ( ++ ( >>> >>> ++
 // ^ A Spoon is missing here! 
type mismatch;
 found   : zio.ZLayer[zio.magic.Example.Spoon.Spoon with Any,Nothing,zio.magic.Example.Cake.Cake with zio.console.Console]
    (which expands to)  zio.ZLayer[zio.Has[zio.magic.Example.Spoon.Service] with Any,Nothing,zio.Has[zio.magic.Example.Cake.Service] with zio.Has[zio.console.Console.Service]]
 required: zio.ULayer[zio.magic.Example.Cake.Cake with zio.console.Console]
    (which expands to)  zio.ZLayer[Any,Nothing,zio.Has[zio.magic.Example.Cake.Service] with zio.Has[zio.console.Console.Service]]
      (( ++ ( >>> >>> ++


You can also directly construct a ZLayer (However you must annotate the call to ZLayer.fromMagic[LikeThis], because macros).

val layer = ZLayer.fromMagic[Flour with Console](,,

To construct URLayer[In, Out] use ZLayer.fromSomeMagic[In, Out] this way:

val layer = ZLayer.fromSomeMagic[CommonEnv, Flour with Console](,,

Alternatively you can provide environment partially with injectSome[Rest](l1, l2, l3) - similarly to .provideSomeLayer.

There's also .injectCustom for which behaves similarly to .provideCustomLayer, only it also provides ZEnv.any to all transitive dependencies.

val program: URIO[Console with Car, Unit] = ???

val carLayer: URLayer[Blocking with Wheels, Car] = ???
val wheelLayer: ULayer[Wheels] = ???

// The ZEnv you use later will provide both Blocking to carLayer and Console to the program
val provided: URIO[ZEnv, Unit] = 
  program.injectCustom(carLayer, wheelLayer)


inject, injectCustom, injectSome, injectShared, injectCustomShared and injectSomeShared all work for zio-test's Spec.


Try ZLayer.wireDebug[Cake] or ZLayer.wireSomeDebug[Blocking with Console, Cake] to print out a pretty tree! Ooh la la!

ZLayer Wiring Graph

 │ ├─◑
 │ │ ╰─◑
 │ ╰─◑

Let me know if you can think of any helpful variants, and I'll give 'em a whirl!