zio icon indicating copy to clipboard operation
zio copied to clipboard

Layer overwrite possible bug

Open adrian-salajan opened this issue 11 months ago • 2 comments

I'm not sure if this is expected behavior or I am missing something about how ZLayers work.

Below code prints:

bbbbb
bbbbb

I would expect it to print:

aaaaa
bbbbb

but somehow the 2nd layer overwrite the first ones, even though they are injected separately

object LayerTest extends ZIOAppDefault {

  case class WrapperA(printer: Printer) {
    def print:UIO[Unit] = printer.print
  }

  object WrapperA {
    val layer = ZLayer.fromFunction(WrapperA(_))
  }

  case class WrapperB(printer: Printer) {
    def print: UIO[Unit] = printer.print
  }

  object WrapperB {
    val layer = ZLayer.fromFunction(WrapperB(_))
  }

  trait Printer {
    def print: UIO[Unit]
  }

  case class PrinterLive(string: String) extends Printer {
    override def print: UIO[Unit] = ZIO.succeed(println(string))
  }

  object PrinterLive {
    val layer: ZLayer[String, Nothing, Printer] = ZLayer.fromFunction(PrinterLive(_))
  }


  val stringLayerA = ZLayer.succeed("aaaaa")
  val stringLayerB = ZLayer.succeed("bbbbb")

  override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = (for {
    a <- ZIO.service[WrapperA]
    b <- ZIO.service[WrapperB]
    _ <- a.print
    _ <- b.print
  } yield ())
    .provide(
      stringLayerA >>> PrinterLive.layer >>> WrapperA.layer,
      stringLayerB >>> PrinterLive.layer >>> WrapperB.layer
    )
}

The fix is to change val into def

  object PrinterLive {
    def layer: ZLayer[String, Nothing, Printer] = ZLayer.fromFunction(PrinterLive(_))
  }

adrian-salajan avatar Mar 15 '24 16:03 adrian-salajan

The more idiomatic solution is to use fresh, I think

    val layer: ZLayer[String, Nothing, Printer] = ZLayer.fromFunction(PrinterLive(_)).fresh

If I remember correctly, by default ZLayer instances are shared. And fresh Creates a fresh version of this layer that will not be shared.

desavitsky avatar Apr 11 '24 09:04 desavitsky

For any given type, you can have at most one service that implements that type. So layer sharing is on by default.

One workaround is to create a case class wrapper that has the modified behavior you need for the subgraph of your application that needs it.

jdegoes avatar May 08 '24 18:05 jdegoes