`@EagerInitialization` is really handy and I would...
# webassembly
j
@EagerInitialization
is really handy and I would like a non-deprecated mechanism for eager set-up in Kotlin/Wasm
Background: I’m doing WIT and I’ve got a
commonMain
generated guest interface like this:
Copy code
public interface Guest {
    public fun sum(a: Long, b: Long): Long
  }
In
wasmWasiMain
I’m generating a
lateinit var
that holds the singleton implementation of this service:
Copy code
public var WasmoTesting.guest: WasmoTesting.Guest
  get() = ...
  set(value) = ...
Finally, in non-generated code I put my user-authored implementation in the generated var:
Copy code
WasmoTesting.guest = object : WasmoTesting.Guest {
    override fun sum(a: Long, b: Long): Long {
      return a + b
    }
  }
Right now I’m using
@EagerInitialization
to trigger that:
Copy code
@EagerInitialization
val initialize = run {
  WasmoTesting.guest = object : WasmoTesting.Guest {
    override fun sum(a: Long, b: Long): Long {
      return a + b
    }
  }
}
I’d be equally happy with another mechanism that allows me to define a symbol in one module, and initialize it in another. Heck, this kind of thing could be compiler-enforced as right now it’s a runtime thing
o
So, what you want is some kind of compile time SPI? :)
j
I'm not quite sure what I want to be honest. I need something that isn't deprecated to do eager initialization.
o
I need something that isn't deprecated to do eager initialization.
AFAIK,
@EagerInitialization
is the only way to do this currently. It's deprecated, true, but the annotation and its behavior will be preserved until a stable alternative appears. A number of libraries in the wild are already using it to get some kind of SPI in KMP. Probably, the most interesting and popular user of it is Ktor (on all targets except for JVM). For now, you can follow "KT-63218 EagerInitialization use cases" and share your use case there. And again, so far, the main use case for this annotation is SPI (KT-53056: Multiplatform equivalent of JVM's ServiceLoader), which is why I'm trying to understand your use case in more detail.
thank you color 1
j
It's a bit like ServiceLoader actually. I've generated a @WasmExport function, and that function needs to discover the implementation to satisfy it
There should be exactly one implementation, and I believe a sufficiently smart linker could detect it during whole-program-analysis
o
There should be exactly one implementation
Yeah, specifically this case is also similar to expect-actual and "KT-79983 # API - Implementation separation(Dependancy Invervesion)". In a more general ServiceLoader approach, though different strategies could be desired, like: • "exactly one" - your use case, Ktor engines, etc • "at most one" - optional things, like logging or tracing backends • "at least one" - cryptography backends, where those could contribute different things, but without them, it will not work at all • "multiple" - different extensions or plugins (e.g., in Dokka or Gradle), interceptors, and so on. Of course, not all of those really need some special handling, as not all of them are very popular in the wild.
I believe a sufficiently smart linker could detect it during whole-program-analysis
And yes, it should be possible to do this on a second compiler stage (e.g., "linking", klibs -> binary). So, thanks for one more use case! But I have a question: why do you want this instance set up implicitly/automatically at build time, rather than via an explicit call in your
main
or other entry-point function?
💯 1
j
With Wasm I have potentially many entry points, each as a @WasmExport function. It's possible to trigger it there, but unfortunate because I gotta be judicious to get each one
o
I think I understand what you mean, but for clarity, could you show me an example of what you would need to do if there were no
@EagerInitialization
in the Kotlin stdlib, and why you don't like it? 🙂
j
wasmo-testing.wit
Api.kt
Guest.kt
Host.kt
I’ve built a thing (Brevity) that takes a WebAssembly .wit file and generates Kotlin for the API (commonMain), the guest (wasmWasiMain) and the host (jvmMain). It’s similar to bindgen in purpose, but it’s all Kotlin and it also generates bindings for the host, which I don’t get with bindgen.
It generates the guest API stubs, but the generated code doesn’t know what the implementation is. And I’m pretty happy with it not knowing what the implementation is. It just generates an interface (
Guest
) and a
lateinit var
that the application layer needs to place the implementation in.
Copy code
// SOMEBODY NEEDS TO SET THIS...
public lateinit var guest: WasmoTesting.Guest

// BEFORE THIS CALL IS EXECUTED
@WasmExport("wasmo:testing/wasmo-testing#sum")
private fun WasmoTesting_guest_sum(a: Long, b: Long): Long = guest.sum(a, b)
I’m able to make it work with
@EagerInitialize
to run an arbitrary initialization block.
Copy code
@OptIn(ExperimentalStdlibApi::class)
@EagerInitialization
val initialize = run {
  guest = object : WasmoTesting.Guest {
    override fun sum(a: Long, b: Long): Long {
      return a + b
    }
  }
}
If I weren’t doing this with codegen it would be a non-issue, cause my entry points could just use the concrete implementation. But I’m generating the entry points in a way that they don’t know the concrete implementation
If Kotlin/Wasm had a service loader mechanism, that could be really nice:
Copy code
public val guest: WasmoTesting.Guest = ServiceLoaderThing.only<WasmoTesting.Guest>()
o
Thanks a lot for the details! Now I understand better what's going on. So, what you really want is to somehow "export" a world in your module. Currently, you do this via
@EagerInitialization
, and if we have an SPI-like solution (like sweet-spi), your "guest impl" code will be like (instead of
@EagerInitialization
):
Copy code
@ServiceProvider(WasmoTesting.Guest::class)
object GuestImpl: WasmoTesting.Guest { /*...*/ }
So, in a nutshell, it's really a "plugin system" where guest implementations are "plugins," and there are no entry points in them to initialize anything, as they are invoked by the "host". Strictly speaking, it's a plugin system inside of a plugin system! Aaaaaand, it looks like for this kind of use case, or any other kind of "plugins",
@EagerInitialization
(or SPI) is the only way. Thanks again! P.S. I'm just trying to categorize different use cases for SPI-related machinery and understand what a good fit for Kotlin is, and what isn't, as many languages in the wild don't really have direct analogs of JDK ServiceLoader, and they live with it, more or less, fine. kodee floating
j
sweet-spi is very nice
kodee pleased 1
I hadn’t seen that before
o
Note the big scary banner there, though. 🙂
j
And it also bottoms out in
@EagerInitialization
!
👌 1