Open benkuly opened 1 year ago
Have you made any progress on this? I'm also investigating WASM JS bindings, and I'm gonna write my proposal for WASM JS implementation in this thread later this week.
No more progess since opening the issue.
I have made some experimentation, and here are my findings. Before proceeding with this idea, some feedback would be really helpful @mhammond @rfk
First of all, I have made some rationalization for why this is a good idea.
uniffi-bindgen-wasm-js
would be an optional addon just like other foreign bindings. No major changes are needed in uniffi-rs
upstream repository.uniffi-rs
scaffolding remains the same, even when you opt to use uniffi-bindgen-wasm-js
. This is different from the first point, because its possible to fork uniffi_bindgen
, and generate entirely different scaffolding for WASM. Forking the scaffolding code does not sound nice, because more changes = more maintenance.wasm_bindgen
should be part of the initial solution, because at the moment its not practical to re-invent and replace wasm_bindgen
(unless of course there is someone bored enough to undertake such a task).wasm_bindgen
ABI, generate a secondary scaffolding file. The consumer would compile the secondary scaffolding file with include!
, just like the primary scaffolding file. For each extern "C"
function in the primary scaffolding file, generate a secondary #[wasm_bindgen] pub fn wasm_*
function, which would be exposed to JS ABI. The implementation of the function would be to call the original function from the primary scaffolding file.wasm_bindgen
is gonna take care of generating "intermediate" JS bindings. For the normal Rust<->Wasm
use case, these bindings are completely adequate, but for Uniffi
these are not adequate bindings. We can piggyback on these bindings to generate Uniffi
bindings.wasm_bindgen
ABI through the "intermediate" bindings generated by wasm_bindgen
.Vec<u8>
to represent RustBuffer
. Use RustBuffer::from_vec
and RustBuffer::destroy_into_vec
to convert between Vec<u8>
and RustBuffer
. This works out very well, because while moving between JS and primary scaffolding, there is only 1 copy being made: the copy by wasm_bindgen
when transitioning between WASM and JS. No additional copy is needed in the primary<->secondary
scaffolding layer, because of RustBuffer::from_vec
and RustBuffer::destroy_into_vec
.-> Result<(), WasmCallStatus>
instead of &mut RustCallStatus
. From my research wasm_bindgen
does not provide built-in support for passing in arbitrary structs as &mut
from JS. However, wasm_bindgen
does provide good enough support for returning Result
. On Rust side, the function simply returns a Result
, where the error is a type convertable to JS ABI. On JS side, when an error is returned, intermediate JS bindings throw the error value. This intermediate error value can be caught, refined into a Uniffi
error class, and rethrown.Uniffi
C ABI exposes objects as pointers, and pointers are easily converted into u64.callback_init
functions using js_sys::Function. This will require some additional scaffolding to transition between js_sys::Function
and function pointers required by primary scaffolding C ABI. All other callback functions use handles (u64), so no special tricks are needed for those functions.At a very high abstract level, this makes perfect sense to me and is roughly the approach @bendk took for the Desktop Firefox JS bindings (although there we generate .webidl bindings and implementation etc). The fact these bindings could be external and the of the implementation: "No major changes are needed in uniffi-rs upstream repository" - what's not to like? :)
It makes a lot of sense to me as well and also reminds me of the Firefox JS work. That ended up working out, so I think this effort could too.
So, would each call to Rust would go like this: JS -> secondary scaffolding -> primary scaffolding -> actual rust function?
I wonder if you could skip the primary scaffolding altogether. Most of the existing complexity is defining FfiConverter
impls for the types. The actual scaffolding calls are fairly easy to put together. I'm not against going through the extra function, I'd just be interested to know what prevents this and if we could update FfiConverter
to fix those issues.
In the secondary scaffolding, use -> Result<(), WasmCallStatus> instead of &mut RustCallStatus. From my research wasm_bindgen does not provide built-in support for passing in arbitrary structs as &mut from JS. However, wasm_bindgen does provide good enough support for returning Result. On Rust side, the function simply returns a Result, where the error is a type convertable to JS ABI. On JS side, when an error is returned, intermediate JS bindings throw the error value. This intermediate error value can be caught, refined into a Uniffi error class, and rethrown.
This is a much nicer API IMO, and it would be nice if the other bindings did something similar. From a quick read, it seems like wasm-bindgen
supports Result<T, JsValue>
. Is there a way to represent a RustBuffer
as a JsValue
?
One thing that will probably be tricky is callback interfaces. I think it should be doable, but a pain.
@bendk you are right, after digging into Rust scaffolding, it looks like it would be better to simply provide an alternate scaffolding implementation for WASM. I will investigate this further.
Yes, RustBuffer
can be converted to JsValue
, but it needs to be annotated with #[wasm_bindgen]
. I would really like to avoid this, because it would mean having to insert WASM code into uniffi-rs
upstream. But I think this can be avoided by simply created an identical WasmBuffer
struct, and WASM scaffolding could simply map between RustBuffer
and WasmBuffer
as needed.
Why do you think that callback interfaces are tricky? Rust can easily call JS by using js_sys::JsFunction.
It looks like my department is deprioritizing WASM JS bindings for this quarter, but I have hope to come back to this within the next few months.
Makes sense to me, excited to see how this progresses.
I don't think there's anything fundamentally hard about callback interfaces, it's just felt like they're a lot of work to implement relative to the rest of the features.
what about using wasmer? https://wasmer.io
what about using wasmer? wasmer.io
What about it? wasmer is a Wasm runtime. It can run any wasm you give it. wasm support in UniFFI would mean you can build it in a way that other parts that can interact with wasm can interact with the UniFFI-powered parts.
Hello @arg0d! Have you had a chance to come back to this? I'd be highly interested.
Hey @gyzerok, wasm bindings were deprioritized completely by the library team :( Due to small size of their library, and due to wasm limitations put on Rust code, it will be easier for them to maintain a separate JS version of the library.
I see thank you!
@bendk in earlier discussion it is mentioned that Mozilla seems to have some uniffi
implementation for WASM used for Firefox desktop. Is there any chance you'd be willing to share those?
I see thank you!
@bendk in earlier discussion it is mentioned that Mozilla seems to have some
uniffi
implementation for WASM used for Firefox desktop. Is there any chance you'd be willing to share those?
(not ben) we don't have any Wasm implementation. There's a custom JS integration in Gecko, but that's very Gecko-specific.
@arg0d as an user I tested wasm-bindgen and I would like to have uniffi-bindgen module with javascript directly inlcuded in order to avoid to keep my single udl file as required code modification for bindings.
This would definitely be super useful. I'm trying to migrate an Android app to Kotlin Multiplatform, including Wasm support, but I have a Rust shared library. Using UniFFI is pretty great so far for both Android, iOS, and Desktop. It's just the WebAssembly integration that's missing.
For full Kotlin Multiplatform support within uniffi-kotlin-multiplatform-bindings I need some sort of WASM support.
My idea was to expose
RustBuffer
,RustCallStatus
and the scaffolding code withwasm-bindgen
, but there are some problems. For examplewasm-bindgen
does not supportMaybeUninit<RustBuffer>
. What do you think about this idea? I don't have enough experience with Rust to be able to assess this.┆Issue is synchronized with this Jira Task ┆Issue Number: UNIFFI-218