7.5 KiB
Customizing import behavior
The #[wasm_bindgen]
macro supports a good amount of configuration for
controlling precisely how imports are imported and what they map to in JS. This
section is intended to hopefully be an exhaustive reference of the
possibilities!
-
module
andversion
- we've seenmodule
so far indicating where we can import items from butversion
is also allowed:#[wasm_bindgen(module = "moment", version = "^2.0.0")] extern { type Moment; fn moment() -> Moment; #[wasm_bindgen(method)] fn format(this: &Moment) -> String; }
The
module
key is used to configure the module that each item is imported from. Theversion
key does not affect the generated wasm itself but rather it's an informative directive for tools like wasm-pack. Tools like wasm-pack will generate apackage.json
for you and theversion
listed here, whenmodule
is also an NPM package, will correspond to what to write down inpackage.json
.In other words the usage of
module
as the name of an NPM package andversion
as the version requirement allows you to, inline in Rust, depend on the NPM ecosystem and import functionality from those packages. When bundled with a tool like wasm-pack everything will automatically get wired up with bundlers and you should be good to go!Note that the
version
is required ifmodule
doesn't start with./
. Ifmodule
starts with./
then it is an error to provide a version.
-
catch
- this attribute allows catching a JS exception. This can be attached to any imported function and the function must return aResult
where theErr
payload is aJsValue
, like so:#[wasm_bindgen] extern { #[wasm_bindgen(catch)] fn foo() -> Result<(), JsValue>; }
If the imported function throws an exception then
Err
will be returned with the exception that was raised, and otherwiseOk
is returned with the result of the function.By default
wasm-bindgen
will take no action when wasm calls a JS function which ends up throwing an exception. The wasm spec right now doesn't support stack unwinding and as a result Rust code will not execute destructors. This can unfortunately cause memory leaks in Rust right now, but as soon as wasm implements catching exceptions we'll be sure to add support as well! -
constructor
- this is used to indicate that the function being bound should actually translate to anew
constructor in JS. The final argument must be a type that's imported from JS, and it's what'll get used in JS:#[wasm_bindgen] extern { type Foo; #[wasm_bindgen(constructor)] fn new() -> Foo; }
This will attach the
new
function to theFoo
type (implied byconstructor
) and in JS when this function is called it will be equivalent tonew Foo()
. -
method
- this is the gateway to adding methods to imported objects or otherwise accessing properties on objects via methods and such. This should be done for doing the equivalent of expressions likefoo.bar()
in JS.#[wasm_bindgen] extern { type Foo; #[wasm_bindgen(method)] fn work(this: &Foo); }
The first argument of a
method
annotation must be a borrowed reference (not mutable, shared) to the type that the method is attached to. In this case we'll be able to call this method likefoo.work()
in JS (wherefoo
has typeFoo
).In JS this invocation will correspond to accessing
Foo.prototype.work
and then calling that when the import is called. Note thatmethod
by default implies going throughprototype
to get a function pointer. -
js_namespace
- this attribute indicates that the JS type is accessed through a particular namespace. For example theWebAssembly.Module
APIs are all accessed through theWebAssembly
namespace. Thejs_namespace
can be applied to any import and whenever the generated JS attempts to reference a name (like a class or function name) it'll be accessed through this namespace.#[wasm_bindgen] extern { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); }
This is an example of how to bind
console.log(x)
in Rust. Thelog
function will be available in the Rust module and will be invoked asconsole.log
in JS. -
getter
andsetter
- these two attributes can be combined withmethod
to indicate that this is a getter or setter method. Agetter
-tagged function by default accesses the JS property with the same name as the getter function. Asetter
's function name is currently required to start with "set_" and the property it accesses is the suffix after "set_".#[wasm_bindgen] extern { type Foo; #[wasm_bindgen(method, getter)] fn property(this: &Foo) -> u32; #[wasm_bindgen(method, setter)] fn set_property(this: &Foo, val: u32); }
Here we're importing the
Foo
type and defining the ability to access each object'sproperty
property. The first function here is a getter and will be available in Rust asfoo.property()
, and the latter is the setter which is accessible asfoo.set_property(2)
. Note that both functions have athis
argument as they're tagged withmethod
.Finally, you can also pass an argument to the
getter
andsetter
properties to configure what property is accessed. When the property is explicitly specified then there is no restriction on the method name. For example the below is equivalent to the above:#[wasm_bindgen] extern { type Foo; #[wasm_bindgen(method, getter = property)] fn assorted_method_name(this: &Foo) -> u32; #[wasm_bindgen(method, setter = "property")] fn some_other_method_name(this: &Foo, val: u32); }
Properties in JS are accessed through
Object.getOwnPropertyDescriptor
. Note that this typically only works for class-like-defined properties which aren't just attached properties on any old object. For accessing any old property on an object we can use... -
structural
- this is a flag tomethod
annotations which indicates that the method being accessed (or property with getters/setters) should be accessed in a structural fashion. For example methods are not accessed throughprototype
and properties are accessed on the object directly rather than throughObject.getOwnPropertyDescriptor
.#[wasm_bindgen] extern { type Foo; #[wasm_bindgen(method, structural)] fn bar(this: &Foo); #[wasm_bindgen(method, getter, structural)] fn baz(this: &Foo) -> u32; }
The type here,
Foo
, is not required to exist in JS (it's not referenced). Instead wasm-bindgen will generate shims that will access the passed in JS value'sbar
property to or thebaz
property (depending on the function). -
js_name = foo
- this can be used to bind to a different function in JS than the identifier that's defined in Rust. For example you can also define multiple signatures for a polymorphic function in JS as well:#[wasm_bindgen] extern { type Foo; #[wasm_bindgen(js_namespace = console, js_name = log)] fn log_string(s: &str); #[wasm_bindgen(js_namespace = console, js_name = log)] fn log_u32(n: u32); #[wasm_bindgen(js_namespace = console, js_name = log)] fn log_many(a: u32, b: JsValue); }
All of these functions will call
console.log
in JS, but each identifier will have only one signature in Rust.