mozilla / uniffi-rs

a multi-language bindings generator for rust
https://mozilla.github.io/uniffi-rs/
Mozilla Public License 2.0
2.48k stars 211 forks source link

Use the `FfiValue` type for all arguments #2162

Open bendk opened 1 week ago

bendk commented 1 week ago

The ffi-buffer feature is an experiment to try to simply bindings generation for languages where we don't have access to ctypes or a similar libffi-based library and where we can't make calls to C functions directly. The only instance that I know of for this is the Gecko-JS bindings, but maybe there are others out there.

For various reasons, that feature is unlikely to be adopted. However, maybe we can take the most important part of it and use it for a general FFI that's used by all languages. That part is the FfiBufferElement type, which is the union on all FFI types that fit in 64-bits.

What if we took that type, renamed it to FfiValue and changed all scaffolding functions so they had signatures of the form: (arg0: FfiValue, arg1: FfiValue, ..., argn: FfiValue, RustCallStatus* out_status) - > FfiValue?

The upside to this is that it because much easier to implement a ctypes-like system on top of this. One of the main sources of complexity for the Gecko-JS bindings is that we need to be able to call all the scaffolding functions from C++ and those functions have so many different signatures. This way the all functions will look more-or-less look the same and the number of signatures is simply N, where N is the maximum number of arguments used. For this same reason, I believe it will lower binary size and memory usage since it reduces the amount of monomorphization.

If we wanted to pass a struct like a RustBuffer then we would pass a pointer to it. For structs with one field, we could consider just passing the value of that field.

If we implement #2160 and use a single RustBuffer for all serialized fields, then we should use the argument to pass a length value -- either the number of items in the vec/hashmap or the total number of bytes reserved in the buffer for that argument.

As long as the machine word size is 64-bits, I believe we won't lose much efficiency compared to today since any argument passed via a register will work exactly the same way. This would probably be worse for 32-bit systems, but I don't think we need to prioritize that.

Using union types may increase the complexity for bindings generation, although I'm not sure about that.

bendk commented 1 week ago

Of all the changes I'm suggesting, this is the one that I'm most torn on. On one hand, I think this would be really useful for the JS bindings. On the other hand, I don't know of any other bindings where this would be useful. On the third hand, maybe if we did have this, then it would be used to implement bindings in a new language or two. I guess my current feeling is that if this isn't that disruptive to other bindings and doesn't hurt performance, then I'm for it but only marginally.