-
Notifications
You must be signed in to change notification settings - Fork 38
Description
When defining a uniffi::Object in Rust, ubrn build web generates both a TypeScript interface and a TypeScript class for the Rust struct.
However, whenever this object is used in records or function signatures, the generated code refers to the interface instead of the actual class.
Since the class has strong typing, but the interface is only a weak type, this results in weaker type guarantees for consumers of the generated bindings.
Here a short example, assume the following rust code:
#[derive(uniffi::Object)]
pub struct MyObject {}
#[derive(uniffi::Record)]
pub struct MyRecord {
member: Arc<MyObject>,
}
#[uniffi::export]
pub fn my_function(my_object: Arc<MyObject>) -> Arc<MyObject> {
my_object
}The ubrn generated typescript code looks like this:
export interface MyObjectInterface {
}
export class MyObject extends UniffiAbstractObject implements MyObjectInterface {
readonly [uniffiTypeNameSymbol] = "MyObject";
readonly [destructorGuardSymbol]: UniffiRustArcPtr;
readonly [pointerLiteralSymbol]: UnsafeMutableRawPointer;
...
}
export type MyRecord = {
member: MyObjectInterface
}
export function myFunction(myObject: MyObjectInterface): MyObjectInterface {
return FfiConverterTypeMyObject.lift(uniffiCaller.rustCall(
/*caller:*/ (callStatus) => {
return nativeModule().ubrn_uniffi_rust_src_fn_func_my_function(
FfiConverterTypeMyObject.lower(myObject),
callStatus);
},
/*liftString:*/ FfiConverterString.lift,
));
}In both the record and the function signature, the generated bindings use MyObjectInterface instead of the actual MyObject class. This makes the API less type-safe than expected, since the interface allows any structurally compatible value, while the class enforces correct usage.
The generated TypeScript should refer to the class (MyObject) wherever the Rust type is Arc, providing stronger typing guarantees for consumers.
Sample code is provided here: https://github.com/wireapp/ubrn-wasm-demo/tree/issue/interfaces