mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-24 10:11:34 +00:00
Merge pull request #664 from fitzgen/guide-serde-arbitrary-data
Guide serde arbitrary data
This commit is contained in:
@ -12,8 +12,7 @@
|
|||||||
- [Passing Rust Closures to JS](./reference/passing-rust-closures-to-js.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)
|
- [Receiving JS Closures in Rust](./reference/receiving-js-closures-in-rust.md)
|
||||||
- [No ES Modules](./reference/no-esm.md)
|
- [No ES Modules](./reference/no-esm.md)
|
||||||
- [Passing Arbitrary data](./reference/passing-data.md)
|
- [Arbitrary Data with Serde](./reference/arbitrary-data-with-serde.md)
|
||||||
- [Feature Reference](./reference/feature-reference.md)
|
|
||||||
- [Command Line Interface](./reference/cli.md)
|
- [Command Line Interface](./reference/cli.md)
|
||||||
- [Supported Types](./reference/types.md)
|
- [Supported Types](./reference/types.md)
|
||||||
- [`#[wasm_bindgen]` Attributes](./reference/attributes/index.md)
|
- [`#[wasm_bindgen]` Attributes](./reference/attributes/index.md)
|
||||||
|
107
guide/src/reference/arbitrary-data-with-serde.md
Normal file
107
guide/src/reference/arbitrary-data-with-serde.md
Normal 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);
|
||||||
|
```
|
@ -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.
|
|
@ -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())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
Reference in New Issue
Block a user