Search code examples
rustwasm-bindgenwasm-pack

When should I call the free() methods generated by wasm-pack?


I wrote some Rust code and compiled it with wasm-pack. I notice these free() methods in the generated .d.ts files:

export class PdfDoc {
  free(): void;
  ...
}

PdfDoc owns a massive amount of memory, up to 1GB, so it's important that all that memory be properly released for reuse when the javascript code is done with it.

Questions:

  • When should I call these free() methods?
  • Do I need to call them explicitly or will they be called automatically?
  • What happens if I never call them?

I searched for "wasm-pack free method" but these combination of search terms didn't find anything useful.


Solution

  • I was wondering the same thing: do I need to carefully pair each new MyStruct() with a call to free() when using wasm-bindgen?

    When should I call these free() methods?

    Call free() before losing the last reference to the JS object wrapper instance, or earlier if you are done using the object.

    Do I need to call them explicitly or will they be called automatically?

    Currently WASM-allocated memory will not free when the JS object wrapper goes out of scope (but s.a. weak references below).

    What happens if I never call them?

    The WASM memory is lost and without a pointer now you won't be able to recover it. This might not be a problem for a fixed or limited number of smaller sized structs, the whole WASM memory is released on unloading the page.

    In more detail:

    Looking at the created bindings we see that the memory allocated in the constructors is not tracked elsewhere and effectively lost if we just forget the returned instance (a JS wrapper object that stores the raw pointer as ptr).

    The wasm-bindgen Guide also hints to this in Support for Weak References mentioning that TC39 weak references is not supported/implemented right now (late 2022):

    Without weak references your JS integration may be susceptible to memory leaks in Rust, for example: You could forget to call .free() on a JS object, leaving the Rust memory allocated.

    The wasm-bindgen Guide example WebAudio shows the usage of free() to prevent leaking memory when repeatedly creating objects that go out of scope. There is at most exactly one (active) object remaining, which mostly reflects your use-case: Cleaning up objects by calling free() when they are not needed anymore and before they go out of scope.

    As an additional aside on careful memory management:

    There might be a design catch to watch out for when using copy-types, consider:

    #[wasm_bindgen]
    #[derive(Clone, Copy)]
    pub struct Bounds {
        width: usize,
        height: usize,
    }
    #[wasm_bindgen]
    impl Bounds {
        // ...
        #[wasm_bindgen(getter)]
        pub fn width(&self) -> usize {
            self.width
        }
    }
    #[wasm_bindgen]
    pub struct MyThing {
        bounds: Bounds,
        // ...
    }
    #[wasm_bindgen]
    impl MyThing {
        // ...
        #[wasm_bindgen(getter)]
        pub fn bounds(&self) -> Bounds {
            self.bounds
        }
    }
    

    which is easily usual and safe code in Rust but here will leak memory if simply used from JS like

    console.log(`Current width is ${myThing.bounds.width} px`);
    

    You might want to watch WASM memory while developing with e.g.

    console.log(`WASM memory usage is ${wasm.memory.buffer.byteLength} bytes`);
    

    Update Feb 2024

    As noted in the comments starting with v0.2.91 "wasm-bindgen does use the TC39 weak references proposal if support is detected. At the time of this writing all major browsers do support it." (wasm-bindgen) I.e. WASM-allocated memory will be automatically freed if the browser supports weak references.