Skip to content

Generated TypeScript uses interface instead of class for uniffi::Object, reducing type safety #328

@fewerner

Description

@fewerner

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions