According to the wit spec, it seems a function can only take parameters of these types
ty ::= 'u8' | 'u16' | 'u32' | 'u64'
| 's8' | 's16' | 's32' | 's64'
| 'float32' | 'float64'
| 'char'
| 'bool'
| 'string'
| tuple
| list
| option
| result
| handle
| id
does this mean we cannot have functions which take functions as arguments? I am trying to figure out how to expose egui bindings to wasm, and the egui api uses a lot of closures.
ui.child_ui(| ui | {
ui.label("hello");
ui.horizontal( | ui | {
ui.label("first");
ui.label("second");
});
});
I don't understand how i can model this in wit files.
As a side note, how do i represent a resource in rust code?
In guest Rust you will typically use a resource wrapper generated by wit-bindgen.
There isn't any straightforward way to pass a closure between a wasm guest and host. There are a couple of ways to pass function pointers depending on what exactly you're trying to do.
The egui snippet you pasted appears to be a builder pattern of sorts. You could probably translate it into a wit interface using resources, which may be what you're getting at.
thanks for the reply. I fell asleep thinking about this :D. egui is not using builds, its just fully based on passing closures.
I was hoping to compile the egui library as a component too and link to guest, but it won't seem to work either, as wit doesn't have the syntax for first class function passing
Maybe it was intentional to not allow functions to travel across component boundary?
Maybe it was intentional to not allow functions to travel across component boundary?
This is a tricky subject. Most languages don't pass actual code around; in Rust's case a closure compiles down roughly to an anonymous function pointer and a struct containing the state captured by the closure. You could perhaps model that as a callback (a tricky subject on its own in the component model) plus a record for state, but that would be a significant R&D project to actually integrate into an existing language.
seems message got corrupted while travelling across the component boundary :upside_down:
understood. I will see if i can contribute/fork egui with a more ffi friendly api and bind that to wasmtime. thanks for the answers <3
code red art has marked this topic as resolved.
Seems to me that WIT resources are exactly the right tool to model callbacks. Say, for example. you wanted to model a callback that takes two parameters, an s32
and a string
, and returns a bool
. The resource would look something like:
resource callback {
call: func(a: s32, b: string) -> bool;
}
The guest can then implement that resource in such a way that it contains a closure containing arbitrary state (e.g. struct MyCallback(Box<dyn Fn(i32, String) -> bool>)
). When the host (or composed component) receives a handle to an instance of that resource, it can call the call
method when appropriate and later drop it when it's no longer needed.
Ah you're right, that wouldn't necessarily be so bad. It would require a different resource type for each unique closure signature but that could be semi-automated as well.
Based on the above snippet:
resource ui {
label: func(label: string)
child-ui: func(cb: ui-callback)
horizontal: func(cb: ui-callback)
}
resource ui-callback {
call(ui: ui)
}
You wouldn't want to have to execute that mess of callbacks very frequently, but hopefully it just builds a config object.
A bit late but even though the problem is basically solved w/ resources, I just had a thought -- what about solving it with WASM?
Assuming a self-hosting interpreter (let's say wasm3
, or some future version of wasmtime
), wouldn't a WIT definition (string
) + some WASM bytes list<u8>
) be a decent approximation for a free floating function? There's a lot of handwaving to do around how you'd deal with imports and the piping but it seems possible
Last updated: Dec 23 2024 at 13:07 UTC