Open pronebird opened 3 months ago
This would be great and something that we've discussed. IMO the best way would be to do this in the uniffi.toml
file, since you may want to rename things differently for different languages. #1426 covers that. Would that work for you or were you hoping for a different system?
It would be cool to support it through proc-macros just like it works for functions:
#[uniffi::export(name = "something")]
fn do_something() {
}
The same API for structs would be:
#[uniffi::export(name = "SomethingElse")]
impl Something {
}
A config file trying to change the same symbol would overwrite the custom name in the macro.
I think having everything in the source code would be the best. Since uniffi.toml
supports mapping for individual languages, I think that the proc macro could account for that and provide additional attributes per language, i.e swift_name = ..
which in absence of name
could take precedence, although I am not sure if it's easy to support in macro rules. But even having a name
attribute would be a great start!
#[uniffi::export(swift_name = "DNSSettings")]
struct DnsSettings {
// fields
}
Can someone explain their use case a bit better? My first question is why not rename the struct in Rust to match what you want in the foreign language?
There's a simple use case of overriding the default CamelCase naming that uniffi exports. For example:
#[derive(uniffi::Object)]
pub struct ABCObject {}; // exported as AbcObject by default
I can think of some names that could benefit from customization: JSONSchema
, DNSSetting
(as @pronebird said), and DHLTrackingCode
(some companies add prefixes to their internal libs).
Wasm-Bindgen has two keywords for that purpose, one for structs and one for impl
blocks. I don't know exactly why they need two, but they're usually paired together:
#[wasm_bindgen(js_name = MyImage)] // avoids conflict with native JS Image
pub struct Image {};
#[wasm_bindgen(js_class = MyImage)]
impl Image {}
My specific use case is in a project that separates the FFI layer from the underlying Rust implementation so that they can evolve separately. The ffi
module is responsible for wrapping some structs and reexporting them to other languages, using uniffi for mobile, wasm_bindgen for web, and extern functions for C.
It's convenient to do everything in one place without polluting the main implementation with annotations. For example:
use crate::Foo; // no annotations in Foo & allows extracting ffi module to another crate
#[repr(C)]
#[cfg_attr(wasm, wasm_bindgen(js_name = Foo))]
#[cfg_attr(mobile, derive(uniffi::Object))] // can't rename
pub struct FooWrapper {
foo: Arc<Mutex<Foo>>,
}
#[cfg_attr(wasm, wasm_bindgen(js_class = Foo))]
#[cfg_attr(mobile, uniffi::export)] // can't rename
impl FooWrapper {
pub fn some_foo_method(&mut self) {
let mut foo = self.foo.lock().unwrap();
foo.some_foo_method();
}
}
#[no_mangle]
pub extern "C" fn foo_some_foo_method() {
FooWrapper::some_foo_method()
}
I could rename the original on import, i.e., use crate::Foo as MyFoo
and then name the wrapper Foo, but that messes with cbindgen (it ignores Rust namespacing and expects unique names for generating C headers).
Thanks for the example, that makes a lot of sense.
It seems like this is very similar to the uniffi.toml
method, but for your use-case it's better to specify the configuration in code rather than in a separate TOML file.
I also feel like swift_name
is better than name
, since you're looking to set the final name without any more case transformations. If we just had a name
field, then the naming would feel wrong if one of the foreign languages didn't use camel-case.
The 2 challenges I see here are:
I do understand that having it in the source seems more convenient, but I'm not sure this convenience factor outweighs the other concerns, nor that having 2 ways of specifying how bindings customize names etc is actually a win - eg, if we take this to the extreme, we could drop uniffi.toml entirely by having every possible binding customization be defined in the source code - but as above, how this might explode when trying to cover all possible bindings and all possible customizations isn't clear, and it's not clear that this specific feature request is more important to specify in the Rust source than any of the other customization options.
About impl blocks, I don't think they need to be renamed. Renaming the struct would suffice. I don't see the use case for renaming impl blocks differently of the struct it implements, being a trait impl or not.
Wasm Bindgen's design choice of renaming impl blocks (and having two different keywords at that) is a bit weird, and I assume it was due to compile-time technical constraints.
So, unless we face the same constraints, I'd prefer the annotation to happen at the struct level only. This would eliminate most of the challenges @mhammond mentioned.
For example:
#[derive(uniffi::Object(name="SomethingElse"))]
struct Something {
}
#[uniffi::export] // no need to rename it here, if technically possible
impl Something {
}
Another issue is that uniffi
is fundamentally different than wasm-bindgen
in the sense that we have multiple language targets, while they have JS only.
This imposes the challenge of potentially having different names for each target:
"python_name"
"kotlin_name"
"swift_name"
"ruby_name"
# 3rd-party
"golang_name"
"csharp_name"
Annotations could become not so pretty:
#[derive(uniffi::Object(python_name="SomethingElse", swift_name="SomethingElseEntirely", kotlin_name="SomethingElse", ruby_name="SomethingElse"))]
struct Something {
}
So, a default "name" could still be helpful:
#[derive(uniffi::Object(name="SomethingElse", swift_name="SomethingElseEntirely"))]
struct Something {
}
And, of course, one could scope the renaming to one target only:
#[derive(uniffi::Object(swift_name="SomethingElseEntirely"))]
struct Something {
}
To be clear, my suggestions are just brainstorming. They'd be convenient as an option, but it would be fine if this feature were available via config files only. I wouldn't mind setting the custom names in uniffi.toml
.
We can continue the discussion at https://github.com/mozilla/uniffi-rs/issues/1426.
Hey @bendk, are there any updates on this? Or #1426 by any chance?
I'm revisiting this issue at work, and it turns out macro annotations would serve us better than a config file. If I contribute to this issue, where should I first look to implement it?
I haven't had time to work on this issue, but I'm currently starting some work that I think will make it much easier to implement these kinds of features and well give me a much better way to answer your question about contributing.
What's your timeline and what language are you hoping to target first? It would be great if you could be a beta tester for the new system.
I don't have a specific timeline. We found a workaround by wrapping everything in a macro; this would remove the macro and make our code cleaner. I currently need it for Kotlin and Swift. Maybe Python in the future.
Hi,
I'd like to be able to tweak the name of structs marked with
uniffi::Record
for Swift. I see that constructors and methods support custom names, but I can't find anything similar for structs.