From 33520d4828784b255004c0f302c339155a1c60e0 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 6 Aug 2018 16:35:28 -0700 Subject: [PATCH 1/4] guide: add exhuastive reference docs for #[wasm_bindgen] attributes --- guide/src/SUMMARY.md | 18 +- guide/src/design/export-customization.md | 85 -------- guide/src/design/import-customization.md | 193 ------------------ guide/src/reference/attributes/index.md | 6 + .../attributes/on-js-imports/catch.md | 29 +++ .../attributes/on-js-imports/constructor.md | 24 +++ .../on-js-imports/getter-and-setter.md | 78 +++++++ .../attributes/on-js-imports/index.md | 5 + .../attributes/on-js-imports/js_class.md | 20 ++ .../attributes/on-js-imports/js_name.md | 46 +++++ .../attributes/on-js-imports/js_namespace.md | 21 ++ .../attributes/on-js-imports/method.md | 26 +++ .../attributes/on-js-imports/module.md | 33 +++ .../on-js-imports/static_method_of.md | 25 +++ .../attributes/on-js-imports/structural.md | 50 +++++ .../attributes/on-rust-exports/constructor.md | 34 +++ .../attributes/on-rust-exports/index.md | 1 + .../attributes/on-rust-exports/js_name.md | 24 +++ .../attributes/on-rust-exports/readonly.md | 39 ++++ 19 files changed, 477 insertions(+), 280 deletions(-) delete mode 100644 guide/src/design/export-customization.md delete mode 100644 guide/src/design/import-customization.md create mode 100644 guide/src/reference/attributes/index.md create mode 100644 guide/src/reference/attributes/on-js-imports/catch.md create mode 100644 guide/src/reference/attributes/on-js-imports/constructor.md create mode 100644 guide/src/reference/attributes/on-js-imports/getter-and-setter.md create mode 100644 guide/src/reference/attributes/on-js-imports/index.md create mode 100644 guide/src/reference/attributes/on-js-imports/js_class.md create mode 100644 guide/src/reference/attributes/on-js-imports/js_name.md create mode 100644 guide/src/reference/attributes/on-js-imports/js_namespace.md create mode 100644 guide/src/reference/attributes/on-js-imports/method.md create mode 100644 guide/src/reference/attributes/on-js-imports/module.md create mode 100644 guide/src/reference/attributes/on-js-imports/static_method_of.md create mode 100644 guide/src/reference/attributes/on-js-imports/structural.md create mode 100644 guide/src/reference/attributes/on-rust-exports/constructor.md create mode 100644 guide/src/reference/attributes/on-rust-exports/index.md create mode 100644 guide/src/reference/attributes/on-rust-exports/js_name.md create mode 100644 guide/src/reference/attributes/on-rust-exports/readonly.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 48c4fb6c..0abd4782 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -15,6 +15,22 @@ - [Feature Reference](./reference/feature-reference.md) - [Command Line Interface](./reference/cli.md) - [Supported Types](./reference/types.md) + - [`#[wasm_bindgen]` Attributes](./reference/attributes/index.md) + - [On JavaScript Imports](./reference/attributes/on-js-imports/index.md) + - [`catch`](./reference/attributes/on-js-imports/catch.md) + - [`constructor`](./reference/attributes/on-js-imports/constructor.md) + - [`getter` and `setter`](./reference/attributes/on-js-imports/getter-and-setter.md) + - [`js_class = "Blah"`](./reference/attributes/on-js-imports/js_class.md) + - [`js_name`](./reference/attributes/on-js-imports/js_name.md) + - [`js_namespace`](./reference/attributes/on-js-imports/js_namespace.md) + - [`method`](./reference/attributes/on-js-imports/method.md) + - [`module = "blah"`](./reference/attributes/on-js-imports/module.md) + - [`structural`](./reference/attributes/on-js-imports/structural.md) + - [`static_method_of = Blah`](./reference/attributes/on-js-imports/static_method_of.md) + - [On Rust Exports](./reference/attributes/on-rust-exports/index.md) + - [`constructor`](./reference/attributes/on-rust-exports/constructor.md) + - [`js_name = Blah`](./reference/attributes/on-rust-exports/js_name.md) + - [`readonly`](./reference/attributes/on-rust-exports/readonly.md) -------------------------------------------------------------------------------- @@ -23,10 +39,8 @@ - [JS Objects in Rust](./design/js-objects-in-rust.md) - [Exporting a function to JS](./design/exporting-rust.md) - [Exporting a struct to JS](./design/exporting-rust-struct.md) - - [Customizing exports](./design/export-customization.md) - [Importing a function from JS](./design/importing-js.md) - [Importing a class from JS](./design/importing-js-struct.md) - - [Customizing imports](./design/import-customization.md) - [Rust Type conversions](./design/rust-type-conversions.md) - [Types in `wasm-bindgen`](./design/describe.md) - [`js-sys`](./js-sys.md) diff --git a/guide/src/design/export-customization.md b/guide/src/design/export-customization.md deleted file mode 100644 index bba032aa..00000000 --- a/guide/src/design/export-customization.md +++ /dev/null @@ -1,85 +0,0 @@ -# Customizing import behavior - -The `#[wasm_bindgen]` macro supports a good amount of configuration for -controlling precisely how exports are exported and what they generate in JS. -This section is intended to hopefully be an exhaustive reference of the -possibilities! - -* `readonly` - when attached to a `pub` struct field this indicates that it's - readonly from JS and a setter will not be generated. - - ```rust - #[wasm_bindgen] - pub struct Foo { - pub first: u32, - #[wasm_bindgen(readonly)] - pub second: u32, - } - ``` - - Here the `first` field will be both readable and writable from JS, but the - `second` field will be a `readonly` field in JS where the setter isn't - implemented and attempting to set it will throw an exception. - -* `constructor` - when attached to a Rust "constructor" it will make the - generated JS bindings callable as `new Foo()`, for example: - - ```rust - #[wasm_bindgen] - pub struct Foo { - contents: u32, - } - - #[wasm_bindgen] - impl Foo { - #[wasm_bindgen(constructor)] - pub fn new() -> Foo { - Foo { contents: 0 } - } - - pub fn get_contents(&self) -> u32 { - self.contents - } - } - ``` - - Here this can be used in JS as: - - ```js - import { Foo } from './my_module'; - - const f = new Foo(); - console.log(f.get_contents()); - ``` - -* `js_name` - this can be used to export a different name in JS than what - something is named in Rust, for example: - - ```rust - #[wasm_bindgen] - pub struct Foo { - contents: u32, - } - - #[wasm_bindgen(js_name = makeFoo)] - pub fn make_foo() -> Foo { - Foo { contents: 6 } - } - - #[wasm_bindgen] - impl Foo { - #[wasm_bindgen(js_name = getContents)] - pub fn get_contents(&self) -> u32 { - self.contents - } - } - ``` - - Here this can be used in JS as: - - ```js - import { makeFoo } from './my_module'; - - const foo = makeFoo(); - console.log(foo.getContents()); - ``` diff --git a/guide/src/design/import-customization.md b/guide/src/design/import-customization.md deleted file mode 100644 index 7deafd68..00000000 --- a/guide/src/design/import-customization.md +++ /dev/null @@ -1,193 +0,0 @@ -# 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! - -* `catch` - this attribute allows catching a JS exception. This can be attached - to any imported function and the function must return a `Result` where the - `Err` payload is a `JsValue`, like so: - - ```rust - #[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 otherwise `Ok` 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 a `new` constructor in JS. The final argument must be a - type that's imported from JS, and it's what'll get used in JS: - - ```rust - #[wasm_bindgen] - extern { - type Foo; - #[wasm_bindgen(constructor)] - fn new() -> Foo; - } - ``` - - This will attach the `new` function to the `Foo` type (implied by - `constructor`) and in JS when this function is called it will be equivalent to - `new 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 like `foo.bar()` in JS. - - ```rust - #[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 like `foo.work()` in JS (where `foo` has - type `Foo`). - - In JS this invocation will correspond to accessing `Foo.prototype.work` and - then calling that when the import is called. Note that `method` by default - implies going through `prototype` to get a function pointer. - -* `js_namespace` - this attribute indicates that the JS type is accessed through - a particular namespace. For example the `WebAssembly.Module` APIs are all - accessed through the `WebAssembly` namespace. The `js_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. - - ```rust - #[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. The `log` function - will be available in the Rust module and will be invoked as `console.log` in - JS. - -* `getter` and `setter` - these two attributes can be combined with `method` to - indicate that this is a getter or setter method. A `getter`-tagged function by - default accesses the JS property with the same name as the getter function. A - `setter`'s function name is currently required to start with "set\_" and the - property it accesses is the suffix after "set\_". - - ```rust - #[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's `property` property. The first function here is a getter and will be - available in Rust as `foo.property()`, and the latter is the setter which is - accessible as `foo.set_property(2)`. Note that both functions have a `this` - argument as they're tagged with `method`. - - Finally, you can also pass an argument to the `getter` and `setter` - 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: - - ```rust - #[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 the `structural` flag. - -* `indexing_getter`, `indexing_setter` and `indexing_deleter` - these three - attributes can be combined with `method` to indicate that this is a indexing - getter, indexing setter or indexing deleter method. They are different from - `getter` and `setter` in a way that `getter` and `setter` can only access - properties that have a name corresponding to the function name or their - argument, but `indexing_getter`, `indexing_setter` and `indexing_deleter` - work in a dynamic manner, similarly to the indexing syntax in JS - (`object[propertyName]`), hence the name. Should always be used together with - the `structural` flag. For example: - - ```rust - #[wasm_bindgen] - extern { - type Foo; - #[wasm_bindgen(method, structural, indexing_getter)] - fn get(this: &Foo, prop: &str) -> u32; - #[wasm_bindgen(method, structural, indexing_setter)] - fn set(this: &Foo, prop: &str, val: u32); - #[wasm_bindgen(method, structural, indexing_deleter)] - fn delete(this: &Foo, prop: &str); - } - ``` - -* `structural` - this is a flag to `method` 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 through - `prototype` and properties are accessed on the object directly rather than - through `Object.getOwnPropertyDescriptor`. - - ```rust - #[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's `bar` property to or the `baz` 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: - - ```rust - #[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. diff --git a/guide/src/reference/attributes/index.md b/guide/src/reference/attributes/index.md new file mode 100644 index 00000000..4399da72 --- /dev/null +++ b/guide/src/reference/attributes/index.md @@ -0,0 +1,6 @@ +# `#[wasm_bindgen]` Attributes + +The `#[wasm_bindgen]` macro supports a good amount of configuration for +controlling precisely how exports are exported, how imports are imported, and +what the generated JavaScript glue ends up looking like. This section is an +exhaustive reference of the possibilities! diff --git a/guide/src/reference/attributes/on-js-imports/catch.md b/guide/src/reference/attributes/on-js-imports/catch.md new file mode 100644 index 00000000..8fa5d9d5 --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/catch.md @@ -0,0 +1,29 @@ +# `catch` + +The `catch` attribute allows catching a JavaScript exception. This can be +attached to any imported function or method, and the function must return a +`Result` where the `Err` payload is a `JsValue`: + +```rust +#[wasm_bindgen] +extern { + // `catch` on a standalone function. + #[wasm_bindgen(catch)] + fn foo() -> Result<(), JsValue>; + + // `catch` on a method. + type Zoidberg; + #[wasm_bindgen(catch, method)] + fn woop_woop_woop(this: &Zoidberg) -> Result; +} +``` + +If calling the imported function throws an exception, then `Err` will be +returned with the exception that was raised. Otherwise, `Ok` 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! diff --git a/guide/src/reference/attributes/on-js-imports/constructor.md b/guide/src/reference/attributes/on-js-imports/constructor.md new file mode 100644 index 00000000..2c88734b --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/constructor.md @@ -0,0 +1,24 @@ +# `constructor` + +The `constructor` attribute is used to indicate that the function being bound +should actually translate to calling the `new` operator in JavaScript. The final +argument must be a type that's imported from JavaScript, and it's what will get +used in the generated glue: + +```rust +#[wasm_bindgen] +extern { + type Shoes; + + #[wasm_bindgen(constructor)] + fn new() -> Shoes; +} +``` + +This will attach a `new` static method to the `Shoes` type, and in JavaScript +when this method is called, it will be equivalent to `new Shoes()`. + +```rust +// Become a cobbler; construct `new Shoes()` +let shoes = Shoes::new(); +``` diff --git a/guide/src/reference/attributes/on-js-imports/getter-and-setter.md b/guide/src/reference/attributes/on-js-imports/getter-and-setter.md new file mode 100644 index 00000000..1e06607f --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/getter-and-setter.md @@ -0,0 +1,78 @@ +# `getter` and `setter` + +These two attributes can be combined with `method` to indicate that this is a +getter or setter method. A `getter`-tagged function by default accesses the +JavaScript property with the same name as the getter function. A `setter`'s +function name is currently required to start with `set_` and the property it +accesses is the suffix after `set\_`. + +Consider the following JavaScript class that has a getter and setter for the +`white_russians` property: + +```js +class TheDude { + get white_russians() { + ... + } + set white_russians(val) { + ... + } +} +``` + +We would import this with the following `#[wasm_bindgen]` attributes: + +```rust +#[wasm_bindgen] +extern { + type TheDude; + + #[wasm_bindgen(method, getter)] + fn white_russians(this: &TheDude) -> u32; + + #[wasm_bindgen(method, setter)] + fn set_white_russians(this: &TheDude, val: u32); +} +``` + +Here we're importing the `TheDude` type and defining the ability to access each +object's `white_russians` property. The first function here is a getter and will +be available in Rust as `the_dude.white_russians()`, and the latter is the +setter which is accessible as `the_dude.set_white_russians(2)`. Note that both +functions have a `this` argument as they're tagged with `method`. + +Finally, you can also pass an argument to the `getter` and `setter` +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: + +```rust +#[wasm_bindgen] +extern { + type TheDude; + + #[wasm_bindgen(method, getter = white_russians)] + fn my_custom_getter_name(this: &TheDude) -> u32; + + #[wasm_bindgen(method, setter = white_russians)] + fn my_custom_setter_name(this: &TheDude, val: u32); +} +``` + +Heads up! `getter` and `setter` functions are found on the constructor's +prototype chain once at load time, cached, and then the cached accessor is +invoked on each access. If you need to dynamically walk the prototype chain on +every access, add the `structural` attribute! + +```js +// This is the default function Rust will invoke on `the_dude.white_russians()`: +const white_russians = Object.getOwnPropertyDescriptor( + TheDude.prototype, + "white_russians" +).get; + +// This is what you get by adding `structural`: +const white_russians = function(the_dude) { + return the_dude.white_russians; +}; +``` diff --git a/guide/src/reference/attributes/on-js-imports/index.md b/guide/src/reference/attributes/on-js-imports/index.md new file mode 100644 index 00000000..99c4097f --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/index.md @@ -0,0 +1,5 @@ +# `#[wasm_bindgen]` on JavaScript Imports + +This section enumerates the attributes available for customizing bindings for +JavaScript functions and classes imported into Rust within an `extern { ... }` +block. diff --git a/guide/src/reference/attributes/on-js-imports/js_class.md b/guide/src/reference/attributes/on-js-imports/js_class.md new file mode 100644 index 00000000..02e6a237 --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/js_class.md @@ -0,0 +1,20 @@ +# `js_class = "Blah"` + +The `js_class` attribute can be used in conjunction with the `method` attribute +to bind methods of imported JavaScript classes that have been renamed on the +Rust side. + +```rust +#[wasm_bindgen] +extern { + // We don't want to import JS strings as `String`, since Rust already has a + // `String` type in its prelude, so rename it as `JsString`. + #[wasm_bindgen(js_name = String)] + type JsString; + + // This is a method on the JavaScript "String" class, so specify that with + // the `js_class` attribute. + #[wasm_bindgen(method, js_class = "String", js_name = charAt)] + fn char_at(this: &JsString, index: u32) -> JsString; +} +``` diff --git a/guide/src/reference/attributes/on-js-imports/js_name.md b/guide/src/reference/attributes/on-js-imports/js_name.md new file mode 100644 index 00000000..f6edcba1 --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/js_name.md @@ -0,0 +1,46 @@ +# `js_name = blah` + +The `js_name` attribute can be used to bind to a different function in +JavaScript than the identifier that's defined in Rust. + +Most often, this is used to convert a camel-cased JavaScript identifier into a +snake-cased Rust identifier: + +```rust +#[wasm_bindgen] +extern { + #[wasm_bindgen(js_name = jsOftenUsesCamelCase)] + fn js_often_uses_camel_case() -> u32; +} +``` + +Sometimes, it is used to bind to JavaScript identifiers that are not valid Rust +identifiers, in which case `js_name = "some string"` is used instead of `js_name += ident`: + +```rust +#[wasm_bindgen] +extern { + #[wasm_bindgen(js_name = "$$$")] + fn cash_money() -> u32; +} +``` +However, you can also use `js_name` to define multiple signatures for +polymorphic JavaScript functions: + +```rust +#[wasm_bindgen] +extern { + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn console_log_str(s: &str); + + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn console_log_u32(n: u32); + + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn console_log_many(a: u32, b: &JsValue); +} +``` + +All of these functions will call `console.log` in JavaScript, but each +identifier will have only one signature in Rust. diff --git a/guide/src/reference/attributes/on-js-imports/js_namespace.md b/guide/src/reference/attributes/on-js-imports/js_namespace.md new file mode 100644 index 00000000..8c44c525 --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/js_namespace.md @@ -0,0 +1,21 @@ +# `js_namespace = blah` + +This attribute indicates that the JavaScript type is accessed through the given +namespace. For example, the `WebAssembly.Module` APIs are all accessed through +the `WebAssembly` namespace. `js_namespace` can be applied to any import +(function or type) and whenever the generated JavaScript attempts to reference a +name (like a class or function name) it'll be accessed through this namespace. + +```rust +#[wasm_bindgen] +extern { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + +log("hello, console!"); +``` + +This is an example of how to bind `console.log` in Rust. The `log` function will +be available in the Rust module and will be invoked as `console.log` in +JavaScript. diff --git a/guide/src/reference/attributes/on-js-imports/method.md b/guide/src/reference/attributes/on-js-imports/method.md new file mode 100644 index 00000000..2f3274fc --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/method.md @@ -0,0 +1,26 @@ +# `method` + +The `method` attribute allows you to describe methods of imported JavaScript +objects. It is applied on a function that has `this` as its first parameter, +which is a shared reference to an imported JavaScript type. + +```rust +#[wasm_bindgen] +extern { + type Set; + + #[wasm_bindgen(method)] + fn has(this: &Set, element: &JsValue) -> bool; +} +``` + +This generates a `has` method on `Set` in Rust, which invokes the +`Set.prototype.has` method in JavaScript. + +```rust +let set: Set = ...; +let elem: JsValue = ...; +if set.has(&elem) { + ... +} +``` diff --git a/guide/src/reference/attributes/on-js-imports/module.md b/guide/src/reference/attributes/on-js-imports/module.md new file mode 100644 index 00000000..4a99dd8b --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/module.md @@ -0,0 +1,33 @@ +# `module = "blah"` + +The `module` attributes configures the module from which items are imported. For +example, + +```rust +#[wasm_bindgen(module = "wu/tang/clan")] +extern { + type ThirtySixChambers; +} +``` + +generates JavaScript import glue like: + +```js +import { ThirtySixChambers } from "wu/tang/clan"; +``` + +If a `module` attribute is not present, then the global scope is used +instead. For example, + +```rust +#[wasm_bindgen] +extern { + fn illmatic() -> u32; +} +``` + +generates JavaScript import glue like: + +```js +let illmatic = this.illmatic; +``` diff --git a/guide/src/reference/attributes/on-js-imports/static_method_of.md b/guide/src/reference/attributes/on-js-imports/static_method_of.md new file mode 100644 index 00000000..40b3ae0d --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/static_method_of.md @@ -0,0 +1,25 @@ +# `static_method_of = Blah` + +The `static_method_of` attribute allows one to specify that an imported function +is a static method of the given imported JavaScript class. For example, to bind +to JavaScript's `Date.now()` static method, one would use this attribute: + +```rust +#[wasm_bindgen] +extern { + type Date; + + #[wasm_bindgen(static_method_of = Date)] + pub fn now() -> f64; +} +``` + +The `now` function becomes a static method of the imported type in the Rust +bindings as well: + +```rust +let instant = Date::now(); +``` + +This is similar to the `js_namespace` attribute, but the usage from within Rust +is different since the method also becomes a static method of the imported type. diff --git a/guide/src/reference/attributes/on-js-imports/structural.md b/guide/src/reference/attributes/on-js-imports/structural.md new file mode 100644 index 00000000..bdcfef60 --- /dev/null +++ b/guide/src/reference/attributes/on-js-imports/structural.md @@ -0,0 +1,50 @@ +# `structural` + +The `structural` flag can be added to `method` annotations, indicating that the +method being accessed (or property with getters/setters) should be accessed in a +structural, duck-type-y fashion. Rather than walking the constructor's prototype +chain once at load time and caching the property result, the prototype chain is +dynamically walked on every access. + +```rust +#[wasm_bindgen] +extern { + type Duck; + + #[wasm_bindgen(method, structural)] + fn quack(this: &Duck); + + #[wasm_bindgen(method, getter, structural)] + fn is_swimming(this: &Duck) -> bool; +} +``` + +The constructor for the type here, `Duck`, is not required to exist in +JavaScript (it's not referenced). Instead `wasm-bindgen` will generate shims +that will access the passed in JavaScript value's `quack` method or its +`is_swimming` property. + +```js +// Without `structural`, get the method directly off the prototype at load time: +const Duck_prototype_quack = Duck.prototype.quack; +function quack(duck) { + Duck_prototype_quack.call(duck); +} + +// With `structural`, walk the prototype chain on every access: +function quack(duck) { + duck.quack(); +} +``` + +## Why don't we always use the `structural` behavior? + +In theory, it is faster since the prototype chain doesn't need to be traversed +every time the method or property is accessed, but today's optimizing JIT +compilers are really good about eliminating that cost. The real reason is to be +future compatible with the ["host bindings" proposal][host-bindings], which +requires that there be no JavaScript shim between the caller and the native host +function. In this scenario, the properties and methods *must* be resolved before +the wasm is instantiated. + +[host-bindings]: https://github.com/WebAssembly/host-bindings/blob/master/proposals/host-bindings/Overview.md diff --git a/guide/src/reference/attributes/on-rust-exports/constructor.md b/guide/src/reference/attributes/on-rust-exports/constructor.md new file mode 100644 index 00000000..1d6fcf4f --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/constructor.md @@ -0,0 +1,34 @@ +# `constructor` + +When attached to a Rust "constructor" it will make the generated JavaScript +bindings callable as `new Foo()`. + +For example, consider this exported Rust type and `constructor` annotation: + +```rust +#[wasm_bindgen] +pub struct Foo { + contents: u32, +} + +#[wasm_bindgen] +impl Foo { + #[wasm_bindgen(constructor)] + pub fn new() -> Foo { + Foo { contents: 0 } + } + + pub fn get_contents(&self) -> u32 { + self.contents + } +} +``` + +This can be used in JavaScript as: + +```js +import { Foo } from './my_module'; + +const f = new Foo(); +console.log(f.get_contents()); +``` diff --git a/guide/src/reference/attributes/on-rust-exports/index.md b/guide/src/reference/attributes/on-rust-exports/index.md new file mode 100644 index 00000000..7e6e130a --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/index.md @@ -0,0 +1 @@ +# `#[wasm_bindgen]` on Rust Exports diff --git a/guide/src/reference/attributes/on-rust-exports/js_name.md b/guide/src/reference/attributes/on-rust-exports/js_name.md new file mode 100644 index 00000000..564d8cd0 --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/js_name.md @@ -0,0 +1,24 @@ +# `js_name = Blah` + +The `js_name` attribute can be used to export a different name in JS than what +something is named in Rust. It can be applied to both exported Rust functions +and types. + +For example, this is often used to convert between Rust's snake-cased +identifiers into JavaScript's camel-cased identifiers: + +```rust +#[wasm_bindgen(js_name = doTheThing)] +pub fn do_the_thing() -> u32 { + 42 +} +``` + +This can be used in JavaScript as: + +```js +import { doTheThing } from './my_module'; + +const x = doTheThing(); +console.log(x); +``` diff --git a/guide/src/reference/attributes/on-rust-exports/readonly.md b/guide/src/reference/attributes/on-rust-exports/readonly.md new file mode 100644 index 00000000..f4f45ef2 --- /dev/null +++ b/guide/src/reference/attributes/on-rust-exports/readonly.md @@ -0,0 +1,39 @@ +# `readonly` + +When attached to a `pub` struct field this indicates that it's read-only from +JavaScript, and a setter will not be generated and exported to JavaScript. + +```rust +#[wasm_bindgen] +pub fn make_foo() -> Foo { + Foo { + first: 10, + second: 20, + } +} + +#[wasm_bindgen] +pub struct Foo { + pub first: u32, + + #[wasm_bindgen(readonly)] + pub second: u32, +} +``` + +Here the `first` field will be both readable and writable from JS, but the +`second` field will be a `readonly` field in JS where the setter isn't +implemented and attempting to set it will throw an exception. + +```js +import { make_foo } from "./my_module"; + +const foo = make_foo(); + +// Can both get and set `first`. +foo.first = 99; +console.log(foo.first); + +// Can only get `second`. +console.log(foo.second); +``` From e22ccb4d5dd211fd7e414bd9e30f942dac455207 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 6 Aug 2018 17:43:36 -0700 Subject: [PATCH 2/4] guide: Clean up passing rust closures to JS section; add passing JS closures to rust section --- guide/src/SUMMARY.md | 3 +- guide/src/reference/closures.md | 65 ---------- .../reference/passing-rust-closures-to-js.md | 118 ++++++++++++++++++ .../receiving-js-closures-in-rust.md | 31 +++++ 4 files changed, 151 insertions(+), 66 deletions(-) delete mode 100644 guide/src/reference/closures.md create mode 100644 guide/src/reference/passing-rust-closures-to-js.md create mode 100644 guide/src/reference/receiving-js-closures-in-rust.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 0abd4782..84ec98d6 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -9,7 +9,8 @@ - [What Just Happened?](./whirlwind-tour/what-just-happened.md) - [What Else Can We Do?](./whirlwind-tour/what-else-can-we-do.md) - [Reference](./reference/index.md) - - [Closures](./reference/closures.md) + - [Passing Rust Closures to JS](./reference/passing-rust-closures-to-js.md) + - [Receiving JS Closures in Rust](./reference/receiving-js-closures-in-rust.md) - [No ES Modules](./reference/no-esm.md) - [Passing Arbitrary data](./reference/passing-data.md) - [Feature Reference](./reference/feature-reference.md) diff --git a/guide/src/reference/closures.md b/guide/src/reference/closures.md deleted file mode 100644 index 1b921505..00000000 --- a/guide/src/reference/closures.md +++ /dev/null @@ -1,65 +0,0 @@ -# Closures - -The `#[wasm_bindgen]` attribute supports some Rust closures being passed to JS. -Examples of what you can do are: - -```rust -#[wasm_bindgen] -extern { - fn foo(a: &Fn()); // could also be `&mut FnMut()` -} -``` - -Here a function `foo` is imported from JS where the first argument is a *stack -closure*. You can call this function with a `&Fn()` argument and JS will receive -a JS function. When the `foo` function returns, however, the JS function will be -invalidated and any future usage of it will raise an exception. - -Closures also support arguments and return values like exports do, for example: - -```rust -#[wasm_bindgen] -extern { - type Foo; - - fn bar(a: &Fn(u32, String) -> Foo); -} -``` - -Sometimes the stack behavior of these closures is not desired. For example you'd -like to schedule a closure to be run on the next turn of the event loop in JS -through `setTimeout`. For this you want the imported function to return but the -JS closure still needs to be valid! - -To support this use case you can do: - -```rust -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern { - fn baz(a: &Closure); -} -``` - -The `Closure` type is defined in the `wasm_bindgen` crate and represents a "long -lived" closure. The JS closure passed to `baz` is still valid after `baz` -returns, and the validity of the JS closure is tied to the lifetime of the -`Closure` in Rust. Once `Closure` is dropped it will deallocate its internal -memory and invalidate the corresponding JS function. - -Like stack closures a `Closure` also supports `FnMut`: - -```rust -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern { - fn another(a: &Closure u32>); -} -``` - -At this time you cannot [pass a JS closure to Rust][cbjs], you can only pass a -Rust closure to JS in limited circumstances. - -[cbjs]: https://github.com/rustwasm/wasm-bindgen/issues/103 diff --git a/guide/src/reference/passing-rust-closures-to-js.md b/guide/src/reference/passing-rust-closures-to-js.md new file mode 100644 index 00000000..fc09fce4 --- /dev/null +++ b/guide/src/reference/passing-rust-closures-to-js.md @@ -0,0 +1,118 @@ +# Passing Rust Closures to Imported JavaScript Functions + +The `#[wasm_bindgen]` attribute supports Rust closures being passed to +JavaScript in two variants: + +1. Stack-lifetime closures that should not be invoked by JavaScript again after + the imported JavaScript function that the closure was passed to returns. + +2. Heap-allocated closures that can be invoked any number of times, but must be + explicitly deallocated when finished. + +## Stack-Lifetime Closures + +Closures with a stack lifetime are passed to JavaScript as either `&Fn` or `&mut +FnMut` trait objects: + +```rust +// Import JS functions that take closures + +#[wasm_bindgen] +extern { + fn takes_immutable_closure(f: &Fn()); + + fn takes_mutable_closure(f: &mut FnMut()); +} + +// Usage + +takes_immutable_closure(&|| { + // ... +}); + +let mut times_called = 0; +takes_mutable_closure(&mut || { + times_called += 1; +}); +``` + +**Once these imported functions return, the closures that were given to them +will become invalidated, and any future attempts to call those closures from +JavaScript will raise an exception.** + +Closures also support arguments and return values like exports do, for example: + +```rust +#[wasm_bindgen] +extern { + fn takes_closure_that_takes_int_and_returns_string(x: &Fn(u32) -> String); +} + +takes_closure_that_takes_int_and_returns_string(&|x: u32| -> String { + format!("x is {}", x) +}); +``` + +## Heap-Allocated Closures + +Sometimes the discipline of stack-lifetime closures is not desired. For example, +you'd like to schedule a closure to be run on the next turn of the event loop in +JavaScript through `setTimeout`. For this, you want the imported function to +return but the JavaScript closure still needs to be valid! + +For this scenario, you need the `Closure` type, which is defined in the +`wasm_bindgen` crate, exported in `wasm_bindgen::prelude`, and represents a +"long lived" closure. + +The validity of the JavaScript closure is tied to the lifetime of the `Closure` +in Rust. **Once a `Closure` is dropped, it will deallocate its internal memory +and invalidate the corresponding JavaScript function so that any further +attempts to invoke it raise an exception.** + +Like stack closures a `Closure` supports both `Fn` and `FnMut` closures, as well +as arguments and returns. + +```rust +#[wasm_bindgen] +extern { + fn setInterval(closure: &Closure, millis: u32) -> f64; + fn cancelInterval(token: f64); + + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + +#[wasm_bindgen] +pub struct Interval { + closure: Closure, + token: f64, +} + +impl Interval { + pub fn new(millis: u32, f: F) -> Interval + where + F: FnMut() + { + // Construct a new closure. + let closure = Closure::new(f); + + // Pass the closuer to JS, to run every n milliseconds. + let token = setInterval(&closure, millis); + + Interval { closure, token } + } +} + +// When the Interval is destroyed, cancel its `setInterval` timer. +impl Drop for Interval { + fn drop(&mut self) { + cancelInterval(self.token); + } +} + +// Keep logging "hello" every second until the resulting `Interval` is dropped. +#[wasm_bindgen] +pub fn hello() -> Interval { + Interval::new(1_000, || log("hello")); +} +``` diff --git a/guide/src/reference/receiving-js-closures-in-rust.md b/guide/src/reference/receiving-js-closures-in-rust.md new file mode 100644 index 00000000..6d78d5ec --- /dev/null +++ b/guide/src/reference/receiving-js-closures-in-rust.md @@ -0,0 +1,31 @@ +# Receiving JavaScript Closures in Exported Rust Functions + +You can use the `js-sys` crate to access JavaScript's `Function` type, and +invoke that function via `Function.prototype.apply` and +`Function.prototype.call`. + +For example, we can wrap a `Vec` in a new type, export it to JavaScript, +and invoke a JavaScript closure on each member of the `Vec`: + +```rust +extern crate js_sys; +extern crate wasm_bindgen; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct VecU32 { + xs: Vec, +} + +#[wasm_bindgen] +impl VecU32 { + pub fn each(&self, f: &js_sys::Function) { + let this = JsValue::NULL; + for x in &self.xs { + let x = JsValue::from(x); + let _ = f.call1(&this, &x); + } + } +} +``` From 451a2a81184c33220d452baa7d2b583ecabb121f Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 7 Aug 2018 11:13:41 -0700 Subject: [PATCH 3/4] guide: Add small intro to Rust export attributes section --- guide/src/reference/attributes/on-rust-exports/index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/guide/src/reference/attributes/on-rust-exports/index.md b/guide/src/reference/attributes/on-rust-exports/index.md index 7e6e130a..f8516cae 100644 --- a/guide/src/reference/attributes/on-rust-exports/index.md +++ b/guide/src/reference/attributes/on-rust-exports/index.md @@ -1 +1,4 @@ # `#[wasm_bindgen]` on Rust Exports + +This section enumerates the attributes available for customizing bindings for +Rust functions and `struct`s exported to JavaScript. From 9d291187c5c9e70d86c1796245bbd33e40ed7c98 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 7 Aug 2018 11:14:00 -0700 Subject: [PATCH 4/4] guide: Fix alphabetizing of attributes --- guide/src/SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 84ec98d6..57246bd4 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -26,8 +26,8 @@ - [`js_namespace`](./reference/attributes/on-js-imports/js_namespace.md) - [`method`](./reference/attributes/on-js-imports/method.md) - [`module = "blah"`](./reference/attributes/on-js-imports/module.md) - - [`structural`](./reference/attributes/on-js-imports/structural.md) - [`static_method_of = Blah`](./reference/attributes/on-js-imports/static_method_of.md) + - [`structural`](./reference/attributes/on-js-imports/structural.md) - [On Rust Exports](./reference/attributes/on-rust-exports/index.md) - [`constructor`](./reference/attributes/on-rust-exports/constructor.md) - [`js_name = Blah`](./reference/attributes/on-rust-exports/js_name.md)