Merge pull request #664 from fitzgen/guide-serde-arbitrary-data

Guide serde arbitrary data
This commit is contained in:
Nick Fitzgerald 2018-08-07 14:48:45 -07:00 committed by GitHub
commit 43636977ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 125 deletions

View File

@ -12,8 +12,7 @@
- [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)
- [Arbitrary Data with Serde](./reference/arbitrary-data-with-serde.md)
- [Command Line Interface](./reference/cli.md)
- [Supported Types](./reference/types.md)
- [`#[wasm_bindgen]` Attributes](./reference/attributes/index.md)

View File

@ -0,0 +1,107 @@
# Serializing and Deserializing Arbitrary Data Into and From `JsValue` with Serde
It's possible to pass arbirtrary data from Rust to JavaScript by serializing it
with [Serde](https://github.com/serde-rs/serde). `wasm-bindgen` includes the
`JsValue` type, which streamlines serializing and deserializing.
## Enable the `"serde-serialize"` Feature
To enable the `"serde-serialize"` feature, do two things in `Cargo.toml`:
1. Add the `serde` and `serde_derive` crates to `[dependencies]`.
2. Add `features = ["serde-serialize"]` to the existing `wasm-bindgen`
dependency.
```toml
[dependencies]
serde = "^1.0.59"
serde_derive = "^1.0.59"
[dependencies.wasm-bindgen]
version = "^0.2"
features = ["serde-serialize"]
```
## Import Serde's Custom-Derive Macros
In your top-level Rust file (e.g. `lib.rs` or `main.rs`), enable the `Serialize`
and `Deserialize` custom-derive macros:
```rust
#[macro_use]
extern crate serde_derive;
```
## Derive the `Serialize` and `Deserialize` Traits
Add `#![derive(Serialize, Deserialize)]` to your type. All of your type's
members must also be supported by Serde, i.e. their types must also implement
the `Serialize` and `Deserialize` traits.
For example, let's say we'd like to pass this `struct` to JavaScript; doing so
is not possible in `wasm-bindgen` normally due to the use of `HashMap`s, arrays,
and nested `Vec`s. None of those types are supported for sending across the wasm
ABI naively, but all of them implement Serde's `Serialize` and `Deserialize`.
Note that we do not need to use the `#[wasm_bindgen]` macro.
```rust
#[derive(Serialize)]
pub struct Example {
pub field1: HashMap<u32, String>,
pub field2: Vec<Vec<f32>>,
pub field3: [f32; 4],
}
```
## Send it to JavaScript with `JsValue::from_serde`
Here's a function that will pass an `Example` to JavaScript by serializing it to
`JsValue`:
```rust
#[wasm_bindgen]
pub fn send_example_to_js() -> JsValue {
let mut field1 = HashMap::new();
field1.insert(0, String::from("ex"));
let example = Example {
field1,
field2: vec![vec![1., 2.], vec![3., 4.]],
field3: [1., 2., 3., 4.]
};
JsValue::from_serde(&example).unwrap()
}
```
## Receive it from JavaScript with `JsValue::into_serde`
Here's a function that will receive a `JsValue` parameter from JavaScript and
then deserialize an `Example` from it:
```rust
#[wasm_bindgen]
pub fn receive_example_from_js(val: &JsValue) {
let example: Example = val.into_serde().unwrap();
...
}
```
## JavaScript Usage
In the `JsValue` that JavaScript gets, `fied1` will be an `Object` (not a
JavaScript `Map`), `field2` will be a JavaScript `Array` whose members are
`Array`s of numbers, and `field3` will be an `Array` of numbers.
```js
import { send_example_to_js, receive_example_from_js } from "example";
// Get the example object from wasm.
let example = send_example_to_js();
// Add another "Vec" element to the end of the "Vec<Vec<f32>>"
example.field2.push([5,6]);
// Send the example object back to wasm.
receive_example_from_js(example);
```

View File

@ -1,51 +0,0 @@
# Feature Reference
Here this section will attempt to be a reference for the various features
implemented in this project. This is likely not exhaustive but the [tests]
should also be a great place to look for examples.
[tests]: https://github.com/rustwasm/wasm-bindgen/tree/master/tests
The `#[wasm_bindgen]` attribute can be attached to functions, structs,
impls, and foreign modules. Impls can only contain functions, and the attribute
cannot be attached to functions in an impl block or functions in a foreign
module. No lifetime parameters or type parameters are allowed on any of these
types. Foreign modules must have the `"C"` abi (or none listed). Free functions
with `#[wasm_bindgen]` might not have the `"C"` abi or none listed, and it's also not
necessary to annotate with the `#[no_mangle]` attribute.
All structs referenced through arguments to functions should be defined in the
macro itself. Arguments allowed implement the `WasmBoundary` trait, and examples
are:
* Integers (u64/i64 require `BigInt` support)
* Floats
* Borrowed strings (`&str`)
* Owned strings (`String`)
* Exported structs (`Foo`, annotated with `#[wasm_bindgen]`)
* Exported C-like enums (`Foo`, annotated with `#[wasm_bindgen]`)
* Imported types in a foreign module annotated with `#[wasm_bindgen]`
* Borrowed exported structs (`&Foo` or `&mut Bar`)
* The `JsValue` type and `&JsValue` (not mutable references)
* Vectors and slices of supported integer types and of the `JsValue` type.
* Optional vectors/slices
All of the above can also be returned except borrowed references. Passing
`Vec<JsValue>` as an argument to a function is not currently supported. Strings are
implemented with shim functions to copy data in/out of the Rust heap. That is, a
string passed to Rust from JS is copied to the Rust heap (using a generated shim
to malloc some space) and then will be freed appropriately.
Owned values are implemented through boxes. When you return a `Foo` it's
actually turned into `Box<RefCell<Foo>>` under the hood and returned to JS as a
pointer. The pointer is to have a defined ABI, and the `RefCell` is to ensure
safety with reentrancy and aliasing in JS. In general you shouldn't see
`RefCell` panics with normal usage.
JS-values-in-Rust are implemented through indexes that index a table generated
as part of the JS bindings. This table is managed via the ownership specified in
Rust and through the bindings that we're returning. More information about this
can be found in the [design doc].
All of these constructs currently create relatively straightforward code on the
JS side of things, mostly having a 1:1 match in Rust with JS.

View File

@ -1,72 +0,0 @@
# Passing arbitrary data to JS
It's possible to pass data from Rust to JS not explicitly supported
in the [Feature Reference](./feature-reference.md) by serializing via [Serde](https://github.com/serde-rs/serde).
`wasm-bindgen` includes the `JsValue` type, which streamlines serializing and deserializing.
In order accomplish this, we must include the serde and serde_derive
crates in `Cargo.toml`, and configure `wasm-bindgen` to work with this feature:
Cargo.toml
```toml
[dependencies]
serde = "^1.0.59"
serde_derive = "^1.0.59"
[dependencies.wasm-bindgen]
version = "^0.2"
features = ["serde-serialize"]
```
In our top-level Rust file (eg `lib.rs` or `main.rs`), we enable the `Serialize`
macro:
```rust
#[macro_use]
extern crate serde_derive;
```
The data we pass at all nesting levels must be supported by serde, or be a `struct` or `enum` that
derives the Serialize trait. For example, let's say we'd like to pass this
struct to JS; doing so is not possible in bindgen directly due to the use
of public fields, `HashMap`s, arrays, and nested `Vec`s. Note that we do not
need to use the `#[wasm_bindgen]` macro.
```rust
#[derive(Serialize)]
pub struct Example {
pub field1: HashMap<u32, String>,
pub field2: Vec<Vec<f32>>,
pub field3: [f32; 4],
}
```
Here's a function that will pass an instance of this `struct` to JS:
```rust
#[wasm_bindgen]
pub fn pass_example() -> JsValue {
let mut field1 = HashMap::new();
field1.insert(0, String::from("ex"));
let example = Example {
field1,
field2: vec![vec![1., 2.], vec![3., 4.]],
field3: [1., 2., 3., 4.]
};
JsValue::from_serde(&example).unwrap()
}
```
When calling this function from JS, its output will automatically be deserialized.
In this example, `fied1` will be a JS `object` (Not a JS `Map`), `field2` will be a
2d JS `array`, and `field3` will be a 1d JS `array`. Example calling code:
```typescript
const rust = import("./from_rust");
rust.then(
r => {
console.log(r.pass_example())
}
)
```