mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-11-28 14:27:36 +03:00
Merge pull request #370 from rustwasm/mdbook-design
Add `design.md` to the online mdbook
This commit is contained in:
commit
d27f5e75c8
@ -14,5 +14,14 @@
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
- [Contributing](./contributing.md)
|
||||
- [Design](./design.md)
|
||||
- [Internal Design](./design.md)
|
||||
- [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)
|
||||
- [Team](./team.md)
|
||||
|
1246
guide/src/design.md
1246
guide/src/design.md
File diff suppressed because it is too large
Load Diff
57
guide/src/design/describe.md
Normal file
57
guide/src/design/describe.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Communicating types to `wasm-bindgen`
|
||||
|
||||
The last aspect to talk about when converting Rust/JS types amongst one another
|
||||
is how this information is actually communicated. The `#[wasm_bindgen]` macro is
|
||||
running over the syntactical (unresolved) structure of the Rust code and is then
|
||||
responsible for generating information that `wasm-bindgen` the CLI tool later
|
||||
reads.
|
||||
|
||||
To accomplish this a slightly unconventional approach is taken. Static
|
||||
information about the structure of the Rust code is serialized via JSON
|
||||
(currently) to a custom section of the wasm executable. Other information, like
|
||||
what the types actually are, unfortunately isn't known until later in the
|
||||
compiler due to things like associated type projections and typedefs. It also
|
||||
turns out that we want to convey "rich" types like `FnMut(String, Foo,
|
||||
&JsValue)` to the `wasm-bindgen` CLI, and handling all this is pretty tricky!
|
||||
|
||||
To solve this issue the `#[wasm_bindgen]` macro generates **executable
|
||||
functions** which "describe the type signature of an import or export". These
|
||||
executable functions are what the `WasmDescribe` trait is all about:
|
||||
|
||||
```rust
|
||||
pub trait WasmDescribe {
|
||||
fn describe();
|
||||
}
|
||||
```
|
||||
|
||||
While deceptively simple this trait is actually quite important. When you write,
|
||||
an export like this:
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen]
|
||||
fn greet(a: &str) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In addition to the shims we talked about above which JS generates the macro
|
||||
*also* generates something like:
|
||||
|
||||
```
|
||||
#[no_mangle]
|
||||
pub extern fn __wbindgen_describe_greet() {
|
||||
<Fn(&str)>::describe();
|
||||
}
|
||||
```
|
||||
|
||||
Or in other words it generates invocations of `describe` functions. In doing so
|
||||
the `__wbindgen_describe_greet` shim is a programmatic description of the type
|
||||
layouts of an import/export. These are then executed when `wasm-bindgen` runs!
|
||||
These executions rely on an import called `__wbindgen_describe` which passes one
|
||||
`u32` to the host, and when called multiple times gives a `Vec<u32>`
|
||||
effectively. This `Vec<u32>` can then be reparsed into an `enum Descriptor`
|
||||
which fully describes a type.
|
||||
|
||||
All in all this is a bit roundabout but shouldn't have any impact on the
|
||||
generated code or runtime at all. All these descriptor functions are pruned from
|
||||
the emitted wasm file.
|
53
guide/src/design/export-customization.md
Normal file
53
guide/src/design/export-customization.md
Normal file
@ -0,0 +1,53 @@
|
||||
# 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());
|
||||
```
|
148
guide/src/design/exporting-rust-struct.md
Normal file
148
guide/src/design/exporting-rust-struct.md
Normal file
@ -0,0 +1,148 @@
|
||||
# Exporting a struct to JS
|
||||
|
||||
So far we've covered JS objects, importing functions, and exporting functions.
|
||||
This has given us quite a rich base to build on so far, and that's great! We
|
||||
sometimes, though, want to go even further and define a JS `class` in Rust. Or
|
||||
in other words, we want to expose an object with methods from Rust to JS rather
|
||||
than just importing/exporting free functions.
|
||||
|
||||
The `#[wasm_bindgen]` attribute can annotate both a `struct` and `impl` blocks
|
||||
to allow:
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen]
|
||||
pub struct Foo {
|
||||
internal: i32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Foo {
|
||||
pub fn new(val: i32) -> Foo {
|
||||
Foo { internal: val }
|
||||
}
|
||||
|
||||
pub fn get(&self) -> i32 {
|
||||
self.internal
|
||||
}
|
||||
|
||||
pub fn set(&mut self, val: i32) {
|
||||
self.internal = val;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is a typical Rust `struct` definition for a type with a constructor and a
|
||||
few methods. Annotating the struct with `#[wasm_bindgen]` means that we'll
|
||||
generate necessary trait impls to convert this type to/from the JS boundary. The
|
||||
annotated `impl` block here means that the functions inside will also be made
|
||||
available to JS through generated shims. If we take a look at the generated JS
|
||||
code for this we'll see:
|
||||
|
||||
```js
|
||||
import * as wasm from './js_hello_world_bg';
|
||||
|
||||
export class Foo {
|
||||
static __construct(ptr) {
|
||||
return new Foo(ptr);
|
||||
}
|
||||
|
||||
constructor(ptr) {
|
||||
this.ptr = ptr;
|
||||
}
|
||||
|
||||
free() {
|
||||
const ptr = this.ptr;
|
||||
this.ptr = 0;
|
||||
wasm.__wbg_foo_free(ptr);
|
||||
}
|
||||
|
||||
static new(arg0) {
|
||||
const ret = wasm.foo_new(arg0);
|
||||
return Foo.__construct(ret)
|
||||
}
|
||||
|
||||
get() {
|
||||
const ret = wasm.foo_get(this.ptr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
set(arg0) {
|
||||
const ret = wasm.foo_set(this.ptr, arg0);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That's actually not much! We can see here though how we've translated from Rust
|
||||
to JS:
|
||||
|
||||
* Associated functions in Rust (those without `self`) turn into `static`
|
||||
functions in JS.
|
||||
* Methods in Rust turn into methods in wasm.
|
||||
* Manual memory management is exposed in JS as well. The `free` function is
|
||||
required to be invoked to deallocate resources on the Rust side of things.
|
||||
|
||||
To be able to use `new Foo()`, you'd need to annotate `new` as `#[wasm_bindgen(constructor)]`.
|
||||
|
||||
One important aspect to note here, though, is that once `free` is called the JS
|
||||
object is "neutered" in that its internal pointer is nulled out. This means that
|
||||
future usage of this object should trigger a panic in Rust.
|
||||
|
||||
The real trickery with these bindings ends up happening in Rust, however, so
|
||||
let's take a look at that.
|
||||
|
||||
```rust
|
||||
// original input to `#[wasm_bindgen]` omitted ...
|
||||
|
||||
#[export_name = "foo_new"]
|
||||
pub extern fn __wasm_bindgen_generated_Foo_new(arg0: i32) -> u32
|
||||
let ret = Foo::new(arg0);
|
||||
Box::into_raw(Box::new(WasmRefCell::new(ret))) as u32
|
||||
}
|
||||
|
||||
#[export_name = "foo_get"]
|
||||
pub extern fn __wasm_bindgen_generated_Foo_get(me: u32) -> i32 {
|
||||
let me = me as *mut WasmRefCell<Foo>;
|
||||
wasm_bindgen::__rt::assert_not_null(me);
|
||||
let me = unsafe { &*me };
|
||||
return me.borrow().get();
|
||||
}
|
||||
|
||||
#[export_name = "foo_set"]
|
||||
pub extern fn __wasm_bindgen_generated_Foo_set(me: u32, arg1: i32) {
|
||||
let me = me as *mut WasmRefCell<Foo>;
|
||||
::wasm_bindgen::__rt::assert_not_null(me);
|
||||
let me = unsafe { &*me };
|
||||
me.borrow_mut().set(arg1);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn __wbindgen_foo_free(me: u32) {
|
||||
let me = me as *mut WasmRefCell<Foo>;
|
||||
wasm_bindgen::__rt::assert_not_null(me);
|
||||
(*me).borrow_mut(); // ensure no active borrows
|
||||
drop(Box::from_raw(me));
|
||||
}
|
||||
```
|
||||
|
||||
As with before this is cleaned up from the actual output but it's the same idea
|
||||
as to what's going on! Here we can see a shim for each function as well as a
|
||||
shim for deallocating an instance of `Foo`. Recall that the only valid wasm
|
||||
types today are numbers, so we're required to shoehorn all of `Foo` into a
|
||||
`u32`, which is currently done via `Box` (like `std::unique_ptr` in C++).
|
||||
Note, though, that there's an extra layer here, `WasmRefCell`. This type is the
|
||||
same as [`RefCell`] and can be mostly glossed over.
|
||||
|
||||
The purpose for this type, if you're interested though, is to uphold Rust's
|
||||
guarantees about aliasing in a world where aliasing is rampant (JS).
|
||||
Specifically the `&Foo` type means that there can be as much alaising as you'd
|
||||
like, but crucially `&mut Foo` means that it is the sole pointer to the data
|
||||
(no other `&Foo` to the same instance exists). The [`RefCell`] type in libstd
|
||||
is a way of dynamically enforcing this at runtime (as opposed to compile time
|
||||
where it usually happens). Baking in `WasmRefCell` is the same idea here,
|
||||
adding runtime checks for aliasing which are typically happening at compile
|
||||
time. This is currently a Rust-specific feature which isn't actually in the
|
||||
`wasm-bindgen` tool itself, it's just in the Rust-generated code (aka the
|
||||
`#[wasm_bindgen]` attribute).
|
||||
|
||||
[`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html
|
120
guide/src/design/exporting-rust.md
Normal file
120
guide/src/design/exporting-rust.md
Normal file
@ -0,0 +1,120 @@
|
||||
# Exporting a function to JS
|
||||
|
||||
Alright now that we've got a good grasp on JS objects and how they're working,
|
||||
let's take a look at another feature of `wasm-bindgen`: exporting functionality
|
||||
with types that are richer than just numbers.
|
||||
|
||||
The basic idea around exporting functionality with more flavorful types is that
|
||||
the wasm exports won't actually be called directly. Instead the generated
|
||||
`foo.js` module will have shims for all exported functions in the wasm module.
|
||||
|
||||
The most interesting conversion here happens with strings so let's take a look
|
||||
at that.
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen]
|
||||
pub fn greet(a: &str) -> String {
|
||||
format!("Hello, {}!", a)
|
||||
}
|
||||
```
|
||||
|
||||
Here we'd like to define an ES module that looks like
|
||||
|
||||
```ts
|
||||
// foo.d.ts
|
||||
export function greet(a: string): string;
|
||||
```
|
||||
|
||||
To see what's going on, let's take a look at the generated shim
|
||||
|
||||
```js
|
||||
import * as wasm from './foo_bg';
|
||||
|
||||
function passStringToWasm(arg) {
|
||||
const buf = new TextEncoder('utf-8').encode(arg);
|
||||
const len = buf.length;
|
||||
const ptr = wasm.__wbindgen_malloc(len);
|
||||
let array = new Uint8Array(wasm.memory.buffer);
|
||||
array.set(buf, ptr);
|
||||
return [ptr, len];
|
||||
}
|
||||
|
||||
function getStringFromWasm(ptr, len) {
|
||||
const mem = new Uint8Array(wasm.memory.buffer);
|
||||
const slice = mem.slice(ptr, ptr + len);
|
||||
const ret = new TextDecoder('utf-8').decode(slice);
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function greet(arg0) {
|
||||
const [ptr0, len0] = passStringToWasm(arg0);
|
||||
try {
|
||||
const ret = wasm.greet(ptr0, len0);
|
||||
const ptr = wasm.__wbindgen_boxed_str_ptr(ret);
|
||||
const len = wasm.__wbindgen_boxed_str_len(ret);
|
||||
const realRet = getStringFromWasm(ptr, len);
|
||||
wasm.__wbindgen_boxed_str_free(ret);
|
||||
return realRet;
|
||||
} finally {
|
||||
wasm.__wbindgen_free(ptr0, len0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Phew, that's quite a lot! We can sort of see though if we look closely what's
|
||||
happening:
|
||||
|
||||
* Strings are passed to wasm via two arguments, a pointer and a length. Right
|
||||
now we have to copy the string onto the wasm heap which means we'll be using
|
||||
`TextEncoder` to actually do the encoding. Once this is done we use an
|
||||
internal function in `wasm-bindgen` to allocate space for the string to go,
|
||||
and then we'll pass that ptr/length to wasm later on.
|
||||
|
||||
* Returning strings from wasm is a little tricky as we need to return a ptr/len
|
||||
pair, but wasm currently only supports one return value (multiple return values
|
||||
[is being standardized](https://github.com/WebAssembly/design/issues/1146)).
|
||||
To work around this in the meantime, we're actually returning a pointer to a
|
||||
ptr/len pair, and then using functions to access the various fields.
|
||||
|
||||
* Some cleanup ends up happening in wasm. The `__wbindgen_boxed_str_free`
|
||||
function is used to free the return value of `greet` after it's been decoded
|
||||
onto the JS heap (using `TextDecoder`). The `__wbindgen_free` is then used to
|
||||
free the space we allocated to pass the string argument once the function call
|
||||
is done.
|
||||
|
||||
Next let's take a look at the Rust side of things as well. Here we'll be looking
|
||||
at a mostly abbreviated and/or "simplified" in the sense of this is what it
|
||||
compiles down to:
|
||||
|
||||
```rust
|
||||
pub extern fn greet(a: &str) -> String {
|
||||
format!("Hello, {}!", a)
|
||||
}
|
||||
|
||||
#[export_name = "greet"]
|
||||
pub extern fn __wasm_bindgen_generated_greet(
|
||||
arg0_ptr: *const u8,
|
||||
arg0_len: usize,
|
||||
) -> *mut String {
|
||||
let arg0 = unsafe {
|
||||
let slice = ::std::slice::from_raw_parts(arg0_ptr, arg0_len);
|
||||
::std::str::from_utf8_unchecked(slice)
|
||||
};
|
||||
let _ret = greet(arg0);
|
||||
Box::into_raw(Box::new(_ret))
|
||||
}
|
||||
```
|
||||
|
||||
Here we can see again that our `greet` function is unmodified and has a wrapper
|
||||
to call it. This wrapper will take the ptr/len argument and convert it to a
|
||||
string slice, while the return value is boxed up into just a pointer and is
|
||||
then returned up to was for reading via the `__wbindgen_boxed_str_*` functions.
|
||||
|
||||
So in general exporting a function involves a shim both in JS and in Rust with
|
||||
each side translating to or from wasm arguments to the native types of each
|
||||
language. The `wasm-bindgen` tool manages hooking up all these shims while the
|
||||
`#[wasm_bindgen]` macro takes care of the Rust shim as well.
|
||||
|
||||
Most arguments have a relatively clear way to convert them, bit if you've got
|
||||
any questions just let me know!
|
||||
|
201
guide/src/design/import-customization.md
Normal file
201
guide/src/design/import-customization.md
Normal file
@ -0,0 +1,201 @@
|
||||
# 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` and `version` - we've seen `module` so far indicating where we can
|
||||
import items from but `version` is also allowed:
|
||||
|
||||
```rust
|
||||
#[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. The `version` 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 a `package.json` for you and the `version` listed here, when
|
||||
`module` is also an NPM package, will correspond to what to write down in
|
||||
`package.json`.
|
||||
|
||||
In other words the usage of `module` as the name of an NPM package and
|
||||
`version` 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* if `module` doesn't start with `./`. If
|
||||
`module` starts with `./` then it is an error to provide a version.
|
||||
|
||||
[wasm-pack]: https://github.com/rustwasm/wasm-pack
|
||||
|
||||
* `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...
|
||||
|
||||
* `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 Rust, but each identifier
|
||||
will have only one signature in Rust.
|
206
guide/src/design/importing-js-struct.md
Normal file
206
guide/src/design/importing-js-struct.md
Normal file
@ -0,0 +1,206 @@
|
||||
# Importing a class from JS
|
||||
|
||||
Just like with functions after we've started exporting we'll also want to
|
||||
import! Now that we've exported a `class` to JS we'll want to also be able to
|
||||
import classes in Rust as well to invoke methods and such. Since JS classes are
|
||||
in general just JS objects the bindings here will look pretty similar to the JS
|
||||
object bindings describe above.
|
||||
|
||||
As usual though, let's dive into an example!
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen(module = "./bar")]
|
||||
extern {
|
||||
type Bar;
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new(arg: i32) -> Bar;
|
||||
|
||||
#[wasm_bindgen(js_namespace = Bar)]
|
||||
fn another_function() -> i32;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn get(this: &Bar) -> i32;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn set(this: &Bar, val: i32);
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn property(this: &Bar) -> i32;
|
||||
|
||||
#[wasm_bindgen(method, setter)]
|
||||
fn set_property(this: &Bar, val: i32);
|
||||
}
|
||||
|
||||
fn run() {
|
||||
let bar = Bar::new(Bar::another_function());
|
||||
let x = bar.get();
|
||||
bar.set(x + 3);
|
||||
|
||||
bar.set_property(bar.property() + 6);
|
||||
}
|
||||
```
|
||||
|
||||
Unlike our previous imports, this one's a bit more chatty! Remember that one of
|
||||
the goals of `wasm-bindgen` is to use native Rust syntax wherever possible, so
|
||||
this is mostly intended to use the `#[wasm_bindgen]` attribute to interpret
|
||||
what's written down in Rust. Now there's a few attribute annotations here, so
|
||||
let's go through one-by-one:
|
||||
|
||||
* `#[wasm_bindgen(module = "./bar")]` - seen before with imports this is declare
|
||||
where all the subsequent functionality is imported form. For example the `Bar`
|
||||
type is going to be imported from the `./bar` module.
|
||||
* `type Bar` - this is a declaration of JS class as a new type in Rust. This
|
||||
means that a new type `Bar` is generated which is "opaque" but is represented
|
||||
as internally containing a `JsValue`. We'll see more on this later.
|
||||
* `#[wasm_bindgen(constructor)]` - this indicates that the binding's name isn't
|
||||
actually used in JS but rather translates to `new Bar()`. The return value of
|
||||
this function must be a bare type, like `Bar`.
|
||||
* `#[wasm_bindgen(js_namespace = Bar)]` - this attribute indicates that the
|
||||
function declaration is namespaced through the `Bar` class in JS.
|
||||
* `#[wasm_bindgen(static_method_of = SomeJsClass)]` - this attribute is similar
|
||||
to `js_namespace`, but instead of producing a free function, produces a static
|
||||
method of `SomeJsClass`.
|
||||
* `#[wasm_bindgen(method)]` - and finally, this attribute indicates that a
|
||||
method call is going to happen. The first argument must be a JS struct, like
|
||||
`Bar`, and the call in JS looks like `Bar.prototype.set.call(...)`.
|
||||
|
||||
With all that in mind, let's take a look at the JS generated.
|
||||
|
||||
```js
|
||||
import * as wasm from './foo_bg';
|
||||
|
||||
import { Bar } from './bar';
|
||||
|
||||
// other support functions omitted...
|
||||
|
||||
export function __wbg_s_Bar_new() {
|
||||
return addHeapObject(new Bar());
|
||||
}
|
||||
|
||||
const another_function_shim = Bar.another_function;
|
||||
export function __wbg_s_Bar_another_function() {
|
||||
return another_function_shim();
|
||||
}
|
||||
|
||||
const get_shim = Bar.prototype.get;
|
||||
export function __wbg_s_Bar_get(ptr) {
|
||||
return shim.call(getObject(ptr));
|
||||
}
|
||||
|
||||
const set_shim = Bar.prototype.set;
|
||||
export function __wbg_s_Bar_set(ptr, arg0) {
|
||||
set_shim.call(getObject(ptr), arg0)
|
||||
}
|
||||
|
||||
const property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').get;
|
||||
export function __wbg_s_Bar_property(ptr) {
|
||||
return property_shim.call(getObject(ptr));
|
||||
}
|
||||
|
||||
const set_property_shim = Object.getOwnPropertyDescriptor(Bar.prototype, 'property').set;
|
||||
export function __wbg_s_Bar_set_property(ptr, arg0) {
|
||||
set_property_shim.call(getObject(ptr), arg0)
|
||||
}
|
||||
```
|
||||
|
||||
Like when importing functions from JS we can see a bunch of shims are generated
|
||||
for all the relevant functions. The `new` static function has the
|
||||
`#[wasm_bindgen(constructor)]` attribute which means that instead of any
|
||||
particular method it should actually invoke the `new` constructor instead (as
|
||||
we see here). The static function `another_function`, however, is dispatched as
|
||||
`Bar.another_function`.
|
||||
|
||||
The `get` and `set` functions are methods so they go through `Bar.prototype`,
|
||||
and otherwise their first argument is implicitly the JS object itself which is
|
||||
loaded through `getObject` like we saw earlier.
|
||||
|
||||
Some real meat starts to show up though on the Rust side of things, so let's
|
||||
take a look:
|
||||
|
||||
```rust
|
||||
pub struct Bar {
|
||||
obj: JsValue,
|
||||
}
|
||||
|
||||
impl Bar {
|
||||
fn new() -> Bar {
|
||||
extern {
|
||||
fn __wbg_s_Bar_new() -> u32;
|
||||
}
|
||||
unsafe {
|
||||
let ret = __wbg_s_Bar_new();
|
||||
Bar { obj: JsValue::__from_idx(ret) }
|
||||
}
|
||||
}
|
||||
|
||||
fn another_function() -> i32 {
|
||||
extern {
|
||||
fn __wbg_s_Bar_another_function() -> i32;
|
||||
}
|
||||
unsafe {
|
||||
__wbg_s_Bar_another_function()
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self) -> i32 {
|
||||
extern {
|
||||
fn __wbg_s_Bar_get(ptr: u32) -> i32;
|
||||
}
|
||||
unsafe {
|
||||
let ptr = self.obj.__get_idx();
|
||||
let ret = __wbg_s_Bar_get(ptr);
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&self, val: i32) {
|
||||
extern {
|
||||
fn __wbg_s_Bar_set(ptr: u32, val: i32);
|
||||
}
|
||||
unsafe {
|
||||
let ptr = self.obj.__get_idx();
|
||||
__wbg_s_Bar_set(ptr, val);
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self) -> i32 {
|
||||
extern {
|
||||
fn __wbg_s_Bar_property(ptr: u32) -> i32;
|
||||
}
|
||||
unsafe {
|
||||
let ptr = self.obj.__get_idx();
|
||||
let ret = __wbg_s_Bar_property(ptr);
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
fn set_property(&self, val: i32) {
|
||||
extern {
|
||||
fn __wbg_s_Bar_set_property(ptr: u32, val: i32);
|
||||
}
|
||||
unsafe {
|
||||
let ptr = self.obj.__get_idx();
|
||||
__wbg_s_Bar_set_property(ptr, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmBoundary for Bar {
|
||||
// ...
|
||||
}
|
||||
|
||||
impl ToRefWasmBoundary for Bar {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
In Rust we're seeing that a new type, `Bar`, is generated for this import of a
|
||||
class. The type `Bar` internally contains a `JsValue` as an instance of `Bar`
|
||||
is meant to represent a JS object stored in our module's stack/slab. This then
|
||||
works mostly the same way that we saw JS objects work in the beginning.
|
||||
|
||||
When calling `Bar::new` we'll get an index back which is wrapped up in `Bar`
|
||||
(which is itself just a `u32` in memory when stripped down). Each function then
|
||||
passes the index as the first argument and otherwise forwards everything along
|
||||
in Rust.
|
75
guide/src/design/importing-js.md
Normal file
75
guide/src/design/importing-js.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Importing a function from JS
|
||||
|
||||
Now that we've exported some rich functionality to JS it's also time to import
|
||||
some! The goal here is to basically implement JS `import` statements in Rust,
|
||||
with fancy types and all.
|
||||
|
||||
First up, let's say we invert the function above and instead want to generate
|
||||
greetings in JS but call it from Rust. We might have, for example:
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen(module = "./greet")]
|
||||
extern {
|
||||
fn greet(a: &str) -> String;
|
||||
}
|
||||
|
||||
fn other_code() {
|
||||
let greeting = greet("foo");
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The basic idea of imports is the same as exports in that we'll have shims in
|
||||
both JS and Rust doing the necessary translation. Let's first see the JS shim in
|
||||
action:
|
||||
|
||||
```js
|
||||
import * as wasm from './foo_bg';
|
||||
|
||||
import { greet } from './greet';
|
||||
|
||||
// ...
|
||||
|
||||
export function __wbg_f_greet(ptr0, len0, wasmretptr) {
|
||||
const [retptr, retlen] = passStringToWasm(greet(getStringFromWasm(ptr0, len0)));
|
||||
(new Uint32Array(wasm.memory.buffer))[wasmretptr / 4] = retlen;
|
||||
return retptr;
|
||||
}
|
||||
```
|
||||
|
||||
The `getStringFromWasm` and `passStringToWasm` are the same as we saw before,
|
||||
and like with `__wbindgen_object_drop_ref` far above we've got this weird export
|
||||
from our module now! The `__wbg_f_greet` function is what's generated by
|
||||
`wasm-bindgen` to actually get imported in the `foo.wasm` module.
|
||||
|
||||
The generated `foo.js` we see imports from the `./greet` module with the `greet`
|
||||
name (was the function import in Rust said) and then the `__wbg_f_greet`
|
||||
function is shimming that import.
|
||||
|
||||
There's some tricky ABI business going on here so let's take a look at the
|
||||
generated Rust as well. Like before this is simplified from what's actually
|
||||
generated.
|
||||
|
||||
```rust
|
||||
extern fn greet(a: &str) -> String {
|
||||
extern {
|
||||
fn __wbg_f_greet(a_ptr: *const u8, a_len: usize, ret_len: *mut usize) -> *mut u8;
|
||||
}
|
||||
unsafe {
|
||||
let a_ptr = a.as_ptr();
|
||||
let a_len = a.len();
|
||||
let mut __ret_strlen = 0;
|
||||
let mut __ret_strlen_ptr = &mut __ret_strlen as *mut usize;
|
||||
let _ret = __wbg_f_greet(a_ptr, a_len, __ret_strlen_ptr);
|
||||
String::from_utf8_unchecked(
|
||||
Vec::from_raw_parts(_ret, __ret_strlen, __ret_strlen)
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here we can see that the `greet` function was generated but it's largely just a
|
||||
shim around the `__wbg_f_greet` function that we're calling. The ptr/len pair
|
||||
for the argument is passed as two arguments and for the return value we're
|
||||
receiving one value (the length) indirectly while directly receiving the
|
||||
returned pointer.
|
238
guide/src/design/js-objects-in-rust.md
Normal file
238
guide/src/design/js-objects-in-rust.md
Normal file
@ -0,0 +1,238 @@
|
||||
# Polyfill for "JS objects in wasm"
|
||||
|
||||
One of the main goals of `wasm-bindgen` is to allow working with and passing
|
||||
around JS objects in wasm, but that's not allowed today! While indeed true,
|
||||
that's where the polyfill comes in.
|
||||
|
||||
The question here is how we shoehorn JS objects into a `u32` for wasm to use.
|
||||
The current strategy for this approach is to maintain two module-local variables
|
||||
in the generated `foo.js` file: a stack and a heap.
|
||||
|
||||
### Temporary JS objects on the stack
|
||||
|
||||
The stack in `foo.js` is, well, a stack. JS objects are pushed on the top of the
|
||||
stack, and their index in the stack is the identifier that's passed to wasm. JS
|
||||
objects are then only removed from the top of the stack as well. This data
|
||||
structure is mainly useful for efficiently passing a JS object into wasm without
|
||||
a sort of "heap allocation". The downside of this, however, is that it only
|
||||
works for when wasm doesn't hold onto a JS object (aka it only gets a
|
||||
"reference" in Rust parlance).
|
||||
|
||||
Let's take a look at an example.
|
||||
|
||||
```rust
|
||||
// foo.rs
|
||||
#[wasm_bindgen]
|
||||
pub fn foo(a: &JsValue) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Here we're using the special `JsValue` type from the `wasm-bindgen` library
|
||||
itself. Our exported function, `foo`, takes a *reference* to an object. This
|
||||
notably means that it can't persist the object past the lifetime of this
|
||||
function call.
|
||||
|
||||
Now what we actually want to generate is a JS module that looks like (in
|
||||
Typescript parlance)
|
||||
|
||||
```ts
|
||||
// foo.d.ts
|
||||
export function foo(a: any);
|
||||
```
|
||||
|
||||
and what we actually generate looks something like:
|
||||
|
||||
```js
|
||||
// foo.js
|
||||
import * as wasm from './foo_bg';
|
||||
|
||||
let stack = [];
|
||||
|
||||
function addBorrowedObject(obj) {
|
||||
stack.push(obj);
|
||||
return stack.length - 1;
|
||||
}
|
||||
|
||||
export function foo(arg0) {
|
||||
const idx0 = addBorrowedObject(arg0);
|
||||
try {
|
||||
wasm.foo(idx0);
|
||||
} finally {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here we can see a few notable points of action:
|
||||
|
||||
* The wasm file was renamed to `foo_bg.wasm`, and we can see how the JS module
|
||||
generated here is importing from the wasm file.
|
||||
* Next we can see our `stack` module variable which is used to push/pop items
|
||||
from the stack.
|
||||
* Our exported function `foo`, takes an arbitrary argument, `arg0`, which is
|
||||
converted to an index with the `addBorrowedObject` object function. The index
|
||||
is then passed to wasm so wasm can operate with it.
|
||||
* Finally, we have a `finally` which frees the stack slot as it's no longer
|
||||
used, issuing a `pop` for what was pushed at the start of the function.
|
||||
|
||||
It's also helpful to dig into the Rust side of things to see what's going on
|
||||
there! Let's take a look at the code that `#[wasm_bindgen]` generates in Rust:
|
||||
|
||||
```rust
|
||||
// what the user wrote
|
||||
pub fn foo(a: &JsValue) {
|
||||
// ...
|
||||
}
|
||||
|
||||
#[export_name = "foo"]
|
||||
pub extern fn __wasm_bindgen_generated_foo(arg0: u32) {
|
||||
let arg0 = unsafe {
|
||||
ManuallyDrop::new(JsValue::__from_idx(arg0))
|
||||
};
|
||||
let arg0 = &*arg0;
|
||||
foo(arg0);
|
||||
}
|
||||
```
|
||||
|
||||
And as with the JS, the notable points here are:
|
||||
|
||||
* The original function, `foo`, is unmodified in the output
|
||||
* A generated function here (with a unique name) is the one that's actually
|
||||
exported from the wasm module
|
||||
* Our generated function takes an integer argument (our index) and then wraps it
|
||||
in a `JsValue`. There's some trickery here that's not worth going into just
|
||||
yet, but we'll see in a bit what's happening under the hood.
|
||||
|
||||
### Long-lived JS objects in a slab
|
||||
|
||||
The above strategy is useful when JS objects are only temporarily used in Rust,
|
||||
for example only during one function call. Sometimes, though, objects may have a
|
||||
dynamic lifetime or otherwise need to be stored on Rust's heap. To cope with
|
||||
this there's a second half of management of JS objects, a slab.
|
||||
|
||||
JS Objects passed to wasm that are not references are assumed to have a dynamic
|
||||
lifetime inside of the wasm module. As a result the strict push/pop of the stack
|
||||
won't work and we need more permanent storage for the JS objects. To cope with
|
||||
this we build our own "slab allocator" of sorts.
|
||||
|
||||
A picture (or code) is worth a thousand words so let's show what happens with an
|
||||
example.
|
||||
|
||||
```rust
|
||||
// foo.rs
|
||||
#[wasm_bindgen]
|
||||
pub fn foo(a: JsValue) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Note that the `&` is missing in front of the `JsValue` we had before, and in
|
||||
Rust parlance this means it's taking ownership of the JS value. The exported ES
|
||||
module interface is the same as before, but the ownership mechanics are slightly
|
||||
different. Let's see the generated JS's slab in action:
|
||||
|
||||
```js
|
||||
import * as wasm from './foo_bg'; // imports from wasm file
|
||||
|
||||
let slab = [];
|
||||
let slab_next = 0;
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (slab_next === slab.length)
|
||||
slab.push(slab.length + 1);
|
||||
const idx = slab_next;
|
||||
const next = slab[idx];
|
||||
slab_next = next;
|
||||
slab[idx] = { obj, cnt: 1 };
|
||||
return idx;
|
||||
}
|
||||
|
||||
export function foo(arg0) {
|
||||
const idx0 = addHeapObject(arg0);
|
||||
wasm.foo(idx0);
|
||||
}
|
||||
|
||||
export function __wbindgen_object_drop_ref(idx) {
|
||||
let obj = slab[idx];
|
||||
obj.cnt -= 1;
|
||||
if (obj.cnt > 0)
|
||||
return;
|
||||
// If we hit 0 then free up our space in the slab
|
||||
slab[idx] = slab_next;
|
||||
slab_next = idx;
|
||||
}
|
||||
```
|
||||
|
||||
Unlike before we're now calling `addHeapObject` on the argument to `foo` rather
|
||||
than `addBorrowedObject`. This function will use `slab` and `slab_next` as a
|
||||
slab allocator to acquire a slot to store the object, placing a structure there
|
||||
once it's found.
|
||||
|
||||
Note here that a reference count is used in addition to storing the object.
|
||||
That's so we can create multiple references to the JS object in Rust without
|
||||
using `Rc`, but it's overall not too important to worry about here.
|
||||
|
||||
Another curious aspect of this generated module is the
|
||||
`__wbindgen_object_drop_ref` function. This is one that's actually imported from
|
||||
wasm rather than used in this module! This function is used to signal the end of
|
||||
the lifetime of a `JsValue` in Rust, or in other words when it goes out of
|
||||
scope. Otherwise though this function is largely just a general "slab free"
|
||||
implementation.
|
||||
|
||||
And finally, let's take a look at the Rust generated again too:
|
||||
|
||||
```rust
|
||||
// what the user wrote
|
||||
pub fn foo(a: JsValue) {
|
||||
// ...
|
||||
}
|
||||
|
||||
#[export_name = "foo"]
|
||||
pub extern fn __wasm_bindgen_generated_foo(arg0: u32) {
|
||||
let arg0 = unsafe {
|
||||
JsValue::__from_idx(arg0)
|
||||
};
|
||||
foo(arg0);
|
||||
}
|
||||
```
|
||||
|
||||
Ah that looks much more familiar! Not much interesting is happening here, so
|
||||
let's move on to...
|
||||
|
||||
### Anatomy of `JsValue`
|
||||
|
||||
Currently the `JsValue` struct is actually quite simple in Rust, it's:
|
||||
|
||||
```rust
|
||||
pub struct JsValue {
|
||||
idx: u32,
|
||||
}
|
||||
|
||||
// "private" constructors
|
||||
|
||||
impl Drop for JsValue {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
__wbindgen_object_drop_ref(self.idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or in other words it's a newtype wrapper around a `u32`, the index that we're
|
||||
passed from wasm. The destructor here is where the `__wbindgen_object_drop_ref`
|
||||
function is called to relinquish our reference count of the JS object, freeing
|
||||
up our slot in the `slab` that we saw above.
|
||||
|
||||
If you'll recall as well, when we took `&JsValue` above we generated a wrapper
|
||||
of `ManuallyDrop` around the local binding, and that's because we wanted to
|
||||
avoid invoking this destructor when the object comes from the stack.
|
||||
|
||||
### Indexing both a slab and the stack
|
||||
|
||||
You might be thinking at this point that this system may not work! There's
|
||||
indexes into both the slab and the stack mixed up, but how do we differentiate?
|
||||
It turns out that the examples above have been simplified a bit, but otherwise
|
||||
the lowest bit is currently used as an indicator of whether you're a slab or a
|
||||
stack index.
|
117
guide/src/design/rust-type-conversions.md
Normal file
117
guide/src/design/rust-type-conversions.md
Normal file
@ -0,0 +1,117 @@
|
||||
# Rust Type conversions
|
||||
|
||||
Previously we've been seeing mostly abridged versions of type conversions when
|
||||
values enter Rust. Here we'll go into some more depth about how this is
|
||||
implemented. There are two categories of traits for converting values, traits
|
||||
for converting values from Rust to JS and traits for the other way around.
|
||||
|
||||
### From Rust to JS
|
||||
|
||||
First up let's take a look at going from Rust to JS:
|
||||
|
||||
```rust
|
||||
pub trait IntoWasmAbi: WasmDescribe {
|
||||
type Abi: WasmAbi;
|
||||
fn into_abi(self, extra: &mut Stack) -> Self::Abi;
|
||||
}
|
||||
```
|
||||
|
||||
And that's it! This is actually the only trait needed currently for translating
|
||||
a Rust value to a JS one. There's a few points here:
|
||||
|
||||
* We'll get to `WasmDescribe` later in this section
|
||||
* The associated type `Abi` is what will actually be generated as an argument to
|
||||
the wasm export. The bound `WasmAbi` is only implemented for types like `u32`
|
||||
and `f64`, those which can be placed on the boundary and transmitted
|
||||
losslessly.
|
||||
* And finally we have the `into_abi` function, returning the `Abi` associated
|
||||
type which will be actually passed to JS. There's also this `Stack` parameter,
|
||||
however. Not all Rust values can be communicated in 32 bits to the `Stack`
|
||||
parameter allows transmitting more data, explained in a moment.
|
||||
|
||||
This trait is implemented for all types that can be converted to JS and is
|
||||
unconditionally used during codegen. For example you'll often see `IntoWasmAbi
|
||||
for Foo` but also `IntoWasmAbi for &'a Foo`.
|
||||
|
||||
The `IntoWasmAbi` trait is used in two locations. First it's used to convert
|
||||
return values of Rust exported functions to JS. Second it's used to convert the
|
||||
Rust arguments of JS functions imported to Rust.
|
||||
|
||||
### From JS to Rust
|
||||
|
||||
Unfortunately the opposite direction from above, going from JS to Rust, is a bit
|
||||
mroe complicated. Here we've got three traits:
|
||||
|
||||
```rust
|
||||
pub trait FromWasmAbi: WasmDescribe {
|
||||
type Abi: WasmAbi;
|
||||
unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self;
|
||||
}
|
||||
|
||||
pub trait RefFromWasmAbi: WasmDescribe {
|
||||
type Abi: WasmAbi;
|
||||
type Anchor: Deref<Target=Self>;
|
||||
unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor;
|
||||
}
|
||||
|
||||
pub trait RefMutFromWasmAbi: WasmDescribe {
|
||||
type Abi: WasmAbi;
|
||||
type Anchor: DerefMut<Target=Self>;
|
||||
unsafe fn ref_mut_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor;
|
||||
}
|
||||
```
|
||||
|
||||
The `FromWasmAbi` is relatively straightforward, basically the opposite of
|
||||
`IntoWasmAbi`. It takes the ABI argument (typically the same as
|
||||
`IntoWasmAbi::Abi`) and then the auxiliary stack to produce an instance of
|
||||
`Self`. This trait is implemented primarily for types that *don't* have internal
|
||||
lifetimes or are references.
|
||||
|
||||
The latter two traits here are mostly the same, and are intended for generating
|
||||
references (both shared and mutable references). They look almost the same as
|
||||
`FromWasmAbi` except that they return an `Anchor` type which implements a
|
||||
`Deref` trait rather than `Self`.
|
||||
|
||||
The `Ref*` traits allow having arguments in functions that are references rather
|
||||
than bare types, for example `&str`, `&JsValue`, or `&[u8]`. The `Anchor` here
|
||||
is required to ensure that the lifetimes don't persist beyond one function call
|
||||
and remain anonymous.
|
||||
|
||||
The `From*` family of traits are used for converting the Rust arguments in Rust
|
||||
exported functions to JS. They are also used for the return value in JS
|
||||
functions imported into Rust.
|
||||
|
||||
### Global stack
|
||||
|
||||
Mentioned above not all Rust types will fit within 32 bits. While we can
|
||||
communicate an `f64` we don't necessarily have the ability to use all the bits.
|
||||
Types like `&str` need to communicate two items, a pointer and a length (64
|
||||
bits). Other types like `&Closure<Fn()>` have even more information to
|
||||
transmit.
|
||||
|
||||
As a result we need a method of communicating more data through the signatures
|
||||
of functions. While we could add more arguments this is somewhat difficult to do
|
||||
in the world of closures where code generation isn't quite as dynamic as a
|
||||
procedural macro. Consequently a "global stack" is used to transmit extra
|
||||
data for a function call.
|
||||
|
||||
The global stack is a fixed-sized static allocation in the wasm module. This
|
||||
stack is temporary scratch space for any one function call from either JS to
|
||||
Rust or Rust ot JS. Both Rust and the JS shim generated have pointers to this
|
||||
global stack and will read/write information from it.
|
||||
|
||||
Using this scheme whenever we want to pass `&str` from JS to Rust we can pass
|
||||
the pointer as the actual ABI argument and the length is then placed in the next
|
||||
spot on the global stack.
|
||||
|
||||
The `Stack` argument to the conversion traits above looks like:
|
||||
|
||||
```rust
|
||||
pub trait Stack {
|
||||
fn push(&mut self, bits: u32);
|
||||
fn pop(&mut self) -> u32;
|
||||
}
|
||||
```
|
||||
|
||||
A trait is used here to facilitate testing but typically the calls don't end up
|
||||
being virtually dispatched at runtime.
|
Loading…
Reference in New Issue
Block a user