wasm-bindgen icon indicating copy to clipboard operation
wasm-bindgen copied to clipboard

__wrap not generated when only #[wasm_bindgen(constructor)] returns Self

Open Larkooo opened this issue 7 months ago • 0 comments

Describe the Bug

wasm-bindgen does not generate the static __wrap(ptr) method for a JavaScript class corresponding to a Rust struct if the only way instances are created is via a #[wasm_bindgen(constructor)] on the Rust side. This occurs even though the generated JavaScript class correctly manages the WASM pointer's lifetime using FinalizationRegistry and includes free() / __destroy_into_raw() methods.

The lack of __wrap seems inconsistent, as the generated JS class is fundamentally wrapping a WASM resource, regardless of whether instances are created solely via the constructor or also returned from other functions.

Steps to Reproduce

  1. Define a Rust struct and mark it with #[wasm_bindgen]:
    use wasm_bindgen::prelude::*;
    
    #[wasm_bindgen]
    pub struct MyThing {
        // internal data
    }
    
    #[wasm_bindgen]
    impl MyThing {
        #[wasm_bindgen(constructor)]
        pub fn new() -> Self {
            // Logic to create the thing
            MyThing { /* ... */ }
        }
    
        // Add some methods that use `&self` or `&mut self`
        #[wasm_bindgen]
        pub fn do_something(&self) {
            // ...
        }
    }
    
    // IMPORTANT: Do NOT add any other function like this:
    // #[wasm_bindgen]
    // pub fn get_a_thing() -> MyThing { /* ... */ }
    
  2. Compile the Rust code to WASM and generate JavaScript bindings using wasm-bindgen.
  3. Inspect the generated JavaScript file (e.g., my_module.js).
  4. Observe that the MyThing class in the JavaScript file is missing the static __wrap(ptr) method, although it will have free(), __destroy_into_raw(), and the corresponding FinalizationRegistry.

Expected Behavior

The generated JavaScript MyThing class should include the static __wrap(ptr) method. Its presence indicates that the class wraps a WASM pointer, which is true even if the only creation path is the constructor. The generation of lifetime management code (FinalizationRegistry, free) further implies that __wrap should logically be present.

Actual Behavior

The generated JavaScript MyThing class lacks the static __wrap(ptr) method.

Additional Context

This issue was encountered when creating bindings for a Provider struct in a Rust library. Instances were only ever created via #[wasm_bindgen(constructor)]. Other structs in the same library, which had functions returning instances (in addition to potentially having constructors), did have the __wrap method generated correctly.

Furthermore, generating __wrap consistently is beneficial in specific environments. For instance, in Unity WebGL builds, JavaScript object references might not persist reliably across certain engine events or callbacks. A common pattern is to use __destroy_into_raw() to obtain the raw WASM pointer (which can be stored as a simple number), and later use __wrap() to reconstruct the JavaScript wrapper object when it's needed again. The absence of __wrap prevents this manual lifetime management pattern for classes that only have a #[wasm_bindgen(constructor)].

A workaround is to add a dummy #[wasm_bindgen] function to the Rust impl block that simply returns Self or a clone, for example:

    #[wasm_bindgen]
    impl MyThing {
        // ... other methods ...

        // Dummy method to force __wrap generation
        #[wasm_bindgen(js_name = internalGetSelf)]
        pub fn internal_get_self(&self) -> Self {
             // Return self or a clone as appropriate
             MyThing { /* ... */ } // Or self.clone() if Clone is implemented
        }
    }

Adding this unnecessary function forces wasm-bindgen to generate the __wrap method in the JavaScript output. This suggests the generation logic for __wrap is currently tied only to non-constructor functions returning the type.

Larkooo avatar Apr 30 '25 08:04 Larkooo