Kotlin/Wasm is an effort driven by the Kotlin team to introduce first class support for Wasm in Kotlin without generating JVM bytecode and by leveraging Wasm GC. I am collaborating with them to move forward Wasm Component Model binding and WASI support on https://github.com/sdeleuze/kowasm.
We have started to explore how WIT can be translated to Kotlin, see WitToKotlin.kt and the related comments. What is interesting with Kotlin is that it provides a more expressive language than Java, closer to what Rust allows in term of expressiveness, but higher level due to his GCed nature.
Please find below a summary of questions/remarks we have, and where we are looking for feedback:
union
looks like a weird one where we struggle to find an elegant translation. It makes sense on Rust but I am wondering how other languages approach it.val
(read-only) rather than var
(mutable) even if this does not look like defined by WIT. Is it ok? Should WIT says something about mutability?List<T>
(similar to what it is in Java) for most cases, for effiency reasons we mapped list of bytes to ByteArray
which is an optimized array of bytes which does not provide growable capabilities. Any concern about that or thoughts about mapping WIT list
to growable heap allocated list versus arrays?but union looks like a weird one where we struggle to find an elegant translation. It makes sense on Rust but I am wondering how other languages approach it.
Hey! I recently worked on Go bindings support for wit-bindgen
. Speaking of union
, variant
, and enum
types, Go as a language does not support any of these types. The way I did is to use struct
to define these types. There are two fields inside the struct, one is Kind
and another is Value
of any
type. Then, use Go's switch
statement, I can simulate "pattern matching" feature that some other languages have, like rust.
For example, the following variant type will be generated as roughly
variant c2 {
a(s32)
b(s64)
c(r)
}
type C2Kind int
const (
C2KindA C2Kind = iota
C2KindB
C2KindR
)
type C2 struct {
kind C2Kind
val any
}
In addition to the struct, the bindgen will also generate accessor methods to this struct, including constructor
, getter
, and setter
s.
Then in the guest code, I can do
switch myC2.Kind() {
case C2KindA:
// do something with myC2.GetA()
case C2KindB:
// do other things
default:
panic("exhausted")
}
Hope this helps!
Kotlin has data classes that map nicely to WIT records but they imply some binary compatiblity issues. We could use regular classes but we would lose features like destructuring and have weaker semantic match with WIT. I am curious if binary compatibility is ensured in other language bindings when a field is added to a record to help us to make an educated choice.
I imagine most source languages won't be able to maintain this kind of binary compatibility.
The component model has a concept of [subtyping] which includes the ability to add fields to records, but I expect it won't usually be implemented with source-language concepts of binary compatibility. Instead, it'll be implemented with component-model-typesystem-aware linking.
[subtyping]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Subtyping.md
We have mapped WIT option to Kotlin null-safety (wrapper less approach idiomatic in Kotlin to express the presence or not of a value)
This sounds great! One question is: Do you have a way to handle option<option<T>>
?
Where mutability had to be expressed, we have chosen to default to val (read-only) rather than var (mutable) even if this does not look like defined by WIT. Is it ok? Should WIT says something about mutability?
All types in wit (other than resources) are pass-by-value, so once a value is received from an interface, code can mutate it or do whatever it wants with the value because it has its own copy.
A sealed class
(or interface) is probably the most "idiomatic" translation of union
, but it requires class wrappers for every variant
Another common approach would be to have a class with a "type" (or similarly named) field plus a nullable getter/setter per variant
(deleted)
Sébastien Deleuze said:
- Most of WIT concepts map nicely to Kotlin, but
union
looks like a weird one where we struggle to find an elegant translation. It makes sense on Rust but I am wondering how other languages approach it.
For the Java binding generator, I treated it like a variant
, generating names for each field, e.g. f0
, f1
...
- We have mapped WIT option to Kotlin null-safety (wrapper less approach idiomatic in Kotlin to express the presence or not of a value)
I did the same for Java, although Dan's point about option<option<T>>
is making me think it needs another look.
- We have mapped at least for now WIT result to thrown exceptions
I think that's the right choice. That's on my TODO list for the Java generator.
- Where mutability had to be expressed, we have chosen to default to
val
(read-only) rather thanvar
(mutable) even if this does not look like defined by WIT. Is it ok? Should WIT says something about mutability?
IMHO, I don't think WIT should have anything to say about mutability for value types, although for resource handles it may be relevant. Value types have copy semantics in WIT, so once a component receives the data, it can do whatever it wants with it. In terms of code generation, I'd say do whatever feels idiomatic for the language.
- While we map WIT list to
List<T>
(similar to what it is in Java) for most cases, for effiency reasons we mapped list of bytes toByteArray
which is an optimized array of bytes which does not provide growable capabilities. Any concern about that or thoughts about mapping WITlist
to growable heap allocated list versus arrays?
This seems related to the mutability question, and again I'd say do whatever is idiomatic.
Dan Gohman said:
We have mapped WIT option to Kotlin null-safety (wrapper less approach idiomatic in Kotlin to express the presence or not of a value)
This sounds great! One question is: Do you have a way to handle
option<option<T>>
?
I think for this possible but uncommon case we would generate a wrapper. Kotlin/JVM can typically use java.util.Optional<T>
for that, but it is not available in Kotlin/Wasm, so we would generate a custom Optional<T>
or similar wrapper for that case.
Thanks everybody for your feedback, super useful.
Last updated: Dec 23 2024 at 12:05 UTC