mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-05-22 18:30:33 +02:00
More concise class
This commit is contained in:
parent
d7579d7be8
commit
62200ff61d
@ -299,18 +299,20 @@
|
|||||||
- [WebAssembly basics](webassembly.md)
|
- [WebAssembly basics](webassembly.md)
|
||||||
- [Load a Wasm module](webassembly/load-wasm-module.md)
|
- [Load a Wasm module](webassembly/load-wasm-module.md)
|
||||||
- [Expose a method](webassembly/expose-method.md)
|
- [Expose a method](webassembly/expose-method.md)
|
||||||
- [Error handling for exposed methods](webassembly/expose-method/error-handling.md)
|
|
||||||
- [Import Method](webassembly/import-method.md)
|
- [Import Method](webassembly/import-method.md)
|
||||||
- [Error handling for imported methods](webassembly/import-method/error-handling.md)
|
|
||||||
- [web-sys](webassembly/import-method/web-sys.md)
|
- [web-sys](webassembly/import-method/web-sys.md)
|
||||||
- [Expose user-defined Rust types](webassembly/expose-rust-type.md)
|
- [Expose user-defined Rust types](webassembly/expose-rust-type.md)
|
||||||
- [Import user-defined Javascript types](webassembly/import-js-type.md)
|
- [Import user-defined Javascript types](webassembly/import-js-type.md)
|
||||||
|
- [Error handling](webassembly/error-handling.md)
|
||||||
|
- [Error handling for imported methods](webassembly/error-handling/imported-methods.md)
|
||||||
|
- [Error handling for exported methods](webassembly/error-handling/exported-methods.md)
|
||||||
- [Limitations](webassembly/limitations.md)
|
- [Limitations](webassembly/limitations.md)
|
||||||
- [Borrow Checker](webassembly/limitations/borrow-checker.md)
|
- [Borrow Checker](webassembly/limitations/borrow-checker.md)
|
||||||
- [Closures](webassembly/limitations/closures.md)
|
- [Closures](webassembly/limitations/closures.md)
|
||||||
- [Async](webassembly/async.md)
|
- [Async](webassembly/async.md)
|
||||||
- [Exercises](exercises/webassembly/webassembly.md)
|
- [Exercises](exercises/webassembly/webassembly.md)
|
||||||
- [Camera](exercises/webassembly/camera.md)
|
- [Camera](exercises/webassembly/camera.md)
|
||||||
|
- [Game Of Life](exercises/webassembly/game-of-life.md)
|
||||||
|
|
||||||
# Final Words
|
# Final Words
|
||||||
|
|
||||||
@ -335,3 +337,6 @@
|
|||||||
- [Bare Metal Rust Afternoon](exercises/bare-metal/solutions-afternoon.md)
|
- [Bare Metal Rust Afternoon](exercises/bare-metal/solutions-afternoon.md)
|
||||||
- [Concurrency Morning](exercises/concurrency/solutions-morning.md)
|
- [Concurrency Morning](exercises/concurrency/solutions-morning.md)
|
||||||
- [Concurrency Afternoon](exercises/concurrency/solutions-afternoon.md)
|
- [Concurrency Afternoon](exercises/concurrency/solutions-afternoon.md)
|
||||||
|
- [Webassembly](exercises/webassembly/webassembly.md)
|
||||||
|
- [Camera](exercises/webassembly/solutions-camera.md)
|
||||||
|
- [Game Of Life](exercises/webassembly/solutions-game-of-life.md)
|
@ -1 +1,13 @@
|
|||||||
# Exercises
|
# Exercises
|
||||||
|
|
||||||
|
There are two exercises for Webassembly, they both live in their own repository inside of
|
||||||
|
[rust-wasm-template](../rust-wasm-template).
|
||||||
|
|
||||||
|
- [The Camera Exercise](camera.md) will give you access to the camera on your computer and offer you to
|
||||||
|
apply transformations on the frames it captures.
|
||||||
|
- [The Game Of Life Exercise](game-of-life.md) will have you implement _John Conway's Game Of Life_ using Webassembly.
|
||||||
|
|
||||||
|
You can find the solutions here:
|
||||||
|
|
||||||
|
- [Camera](solutions-camera.md)
|
||||||
|
- [Game Of Life](solutions-game-of-life.md)
|
||||||
|
@ -22,14 +22,15 @@ tokio = { version = "1.29.1", features = ["sync"] }
|
|||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
features = [
|
features = [
|
||||||
|
'CanvasRenderingContext2d',
|
||||||
'CssStyleDeclaration',
|
'CssStyleDeclaration',
|
||||||
'Document',
|
'Document',
|
||||||
'Element',
|
'Element',
|
||||||
'ImageData',
|
'ImageData',
|
||||||
'CanvasRenderingContext2d',
|
|
||||||
'HtmlCanvasElement',
|
'HtmlCanvasElement',
|
||||||
'HtmlSelectElement',
|
|
||||||
'HtmlElement',
|
'HtmlElement',
|
||||||
|
'HtmlSelectElement',
|
||||||
'Node',
|
'Node',
|
||||||
|
'Response',
|
||||||
'Window',
|
'Window',
|
||||||
]
|
]
|
||||||
|
@ -1,78 +1,36 @@
|
|||||||
# Async
|
# Async
|
||||||
|
|
||||||
Rust methods in WebAssembly can be declared async. Once called, they will be scheduled on the browser's event loop.
|
Rust methods in WebAssembly can be declared async.
|
||||||
An event handler can for instance be implemented with a tokio channel.
|
|
||||||
|
|
||||||
Instead of `tokio::spawn`, `wasm_bindgen` provides `wasm_bindgen_futures::spawn_local`.
|
|
||||||
|
|
||||||
Let's create a class that waits for messages on a channel to rotate an HTML element:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen_futures::spawn_local;
|
use wasm_bindgen_futures::JsFuture;
|
||||||
use tokio::sync::mpsc::{channel, Sender};
|
use web_sys::Response;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum RotateSide {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct Rotator {
|
pub async fn get_current_page() -> Result<JsValue, JsValue> {
|
||||||
sender: Sender<RotateSide>,
|
let window = web_sys::window().unwrap();
|
||||||
}
|
let resp_value = JsFuture::from(window.fetch_with_str("")).await?;
|
||||||
|
let resp: Response = resp_value.dyn_into().unwrap();
|
||||||
#[wasm_bindgen]
|
let text = JsFuture::from(resp.text()?).await?;
|
||||||
impl Rotator {
|
Ok(text)
|
||||||
#[wasm_bindgen(constructor)]
|
|
||||||
pub fn new(element: web_sys::HtmlElement) -> Rotator {
|
|
||||||
let (sender, mut receiver) = channel::<RotateSide>(1);
|
|
||||||
spawn_local(async move {
|
|
||||||
let mut rotation = 0;
|
|
||||||
while let Some(rotate_side) = receiver.recv().await {
|
|
||||||
match rotate_side {
|
|
||||||
RotateSide::Left => rotation -= 45,
|
|
||||||
RotateSide::Right => rotation += 45,
|
|
||||||
}
|
|
||||||
element.set_inner_html(&rotation.to_string());
|
|
||||||
let style = element.style();
|
|
||||||
style
|
|
||||||
.set_property("transform", &format!("rotate({rotation}deg)"))
|
|
||||||
.expect("Failed to rotate");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Rotator { sender }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub async fn rotate(&self, msg: String) -> Result<(), JsValue> {
|
|
||||||
let rotate_side = match msg.as_str() {
|
|
||||||
"ArrowLeft" => RotateSide::Left,
|
|
||||||
"ArrowRight" => RotateSide::Right,
|
|
||||||
_ => return Ok(()),
|
|
||||||
};
|
|
||||||
self.sender
|
|
||||||
.send(rotate_side)
|
|
||||||
.await
|
|
||||||
.map_err(|e| JsValue::from_str(&format!("Receiver dropped {:?}", e)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's call it from Javascript
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import init, {Rotator} from '/wasm/project.js';
|
import init, { get_current_page} from '/wasm/project.js';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Run the init method to initiate the WebAssembly module.
|
|
||||||
await init();
|
await init();
|
||||||
const wasmoutput = document.querySelector('#wasmoutput');
|
console.log(await get_current_page());
|
||||||
const rotator = new Rotator(wasmoutput);
|
|
||||||
document.body.addEventListener('keydown', async (e) => {
|
|
||||||
await rotator.rotate(e.key);
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
- Async methods are scheduled on the Javascript event loop.
|
||||||
|
- Instead of `tokio::spawn`, `wasm_bindgen` provides `wasm_bindgen_futures::spawn_local`.
|
||||||
|
- We use `JsFuture::from` to convert Javascript futures to Rust futures that we can `.await`.
|
||||||
|
|
||||||
|
</details>
|
7
src/webassembly/error-handling.md
Normal file
7
src/webassembly/error-handling.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Error handling
|
||||||
|
|
||||||
|
In this chapter we cover error handling both on the Rust side for imported Javascript methods
|
||||||
|
and on the Javascript side for imported Rust methods.
|
||||||
|
|
||||||
|
- [Error handling for imported methods](error-handling/imported-methods.md)
|
||||||
|
- [Error handling for exported methods](error-handling/exported-methods.md)
|
@ -24,13 +24,12 @@ pub fn str_to_int(s: &str) -> Option<i32> {
|
|||||||
Javascript, click on the wasm output box to parse the string:
|
Javascript, click on the wasm output box to parse the string:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import init, {set_panic_hook, str_to_int} from '/wasm/project.js';
|
import init, {str_to_int} from '/wasm/project.js';
|
||||||
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Run the init method to initiate the WebAssembly module.
|
// Run the init method to initiate the WebAssembly module.
|
||||||
await init();
|
await init();
|
||||||
set_panic_hook();
|
|
||||||
const wasmoutput = document.querySelector('#wasmoutput');
|
const wasmoutput = document.querySelector('#wasmoutput');
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'text';
|
input.type = 'text';
|
||||||
@ -51,4 +50,4 @@ import init, {set_panic_hook, str_to_int} from '/wasm/project.js';
|
|||||||
* Click on the wasm output box to see the output
|
* Click on the wasm output box to see the output
|
||||||
* `?` and other error handling tools are also supported
|
* `?` and other error handling tools are also supported
|
||||||
|
|
||||||
</details>
|
</details>
|
@ -24,6 +24,6 @@ pub fn add(a: i32, b: i32) -> i32 {
|
|||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
* `set_panic_hook` is a convenient setup method that adds debug information to stack traces when a Wasm module panics. Don't use it in prod builds because it is rather
|
* `set_panic_hook` is a convenient setup method that adds debug information to stack traces when a Wasm module panics. Don't use it in prod builds because it tends to bloat the bundle size.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
@ -2,40 +2,26 @@
|
|||||||
|
|
||||||
Similarily to methods, types can be exposed from Rust to Javascript with the `#[wasm_bindgen]` macro.
|
Similarily to methods, types can be exposed from Rust to Javascript with the `#[wasm_bindgen]` macro.
|
||||||
|
|
||||||
Members that implement `Copy` can be public and directly accessed from Javascript.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct Counter {
|
pub struct Counter {
|
||||||
name: String,
|
name: String,
|
||||||
pub count: u8,
|
pub count: u8,
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
Methods can also be exported
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl Counter {
|
impl Counter {
|
||||||
// Constructor will be called in JS when using `new Counter(name, count)`
|
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(name: String, count: u8) -> Counter {
|
pub fn new(name: String, count: u8) -> Counter {
|
||||||
Counter { name, count }
|
Counter { name, count }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn increment(&mut self) {
|
pub fn increment(&mut self) {
|
||||||
self.count += 1;
|
self.count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getter for the name
|
|
||||||
#[wasm_bindgen(getter)]
|
#[wasm_bindgen(getter)]
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> String {
|
||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setter for the name
|
|
||||||
#[wasm_bindgen(setter)]
|
#[wasm_bindgen(setter)]
|
||||||
pub fn set_name(&mut self, name: String) {
|
pub fn set_name(&mut self, name: String) {
|
||||||
self.name = name;
|
self.name = name;
|
||||||
@ -43,23 +29,18 @@ impl Counter {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Add this button to the HTML file
|
Javascript to use the `Counter`.
|
||||||
|
|
||||||
```html
|
|
||||||
<button id="button">Increment</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
Javascript to use the `Counter`
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import init, { set_panic_hook, Counter } from "/wasm/project.js";
|
import init, { Counter } from "/wasm/project.js";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Run the init method to initiate the WebAssembly module.
|
// Run the init method to initiate the WebAssembly module.
|
||||||
await init();
|
await init();
|
||||||
set_panic_hook();
|
|
||||||
const wasmOutput = document.querySelector("#wasmoutput");
|
const wasmOutput = document.querySelector("#wasmoutput");
|
||||||
const button = document.querySelector("#button");
|
const button = document.createElement("button");
|
||||||
|
button.textContent = "increment";
|
||||||
|
document.body.appendChild(button);
|
||||||
const counter = new Counter("ButtonCounter", 42);
|
const counter = new Counter("ButtonCounter", 42);
|
||||||
wasmOutput.textContent = counter.count;
|
wasmOutput.textContent = counter.count;
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
@ -75,5 +56,5 @@ import init, { set_panic_hook, Counter } from "/wasm/project.js";
|
|||||||
|
|
||||||
- `pub` members must implement copy
|
- `pub` members must implement copy
|
||||||
- Type parameters and lifetime annotations are not supported yet
|
- Type parameters and lifetime annotations are not supported yet
|
||||||
|
- Members that implement `Copy` can be public and directly accessed from Javascript.
|
||||||
</details>
|
</details>
|
||||||
|
@ -1,69 +1,68 @@
|
|||||||
# Import user-defined Javascript types
|
# Import user-defined Javascript types
|
||||||
|
|
||||||
User-defined Javascript types can be imported by declaring the relevant methods as `extern "C"` just like
|
User-defined Javascript types can be imported by declaring the relevant methods as `extern "C"` just like
|
||||||
other foreign functions.
|
other foreign functions.
|
||||||
|
|
||||||
For instance, let's declare a class `OutputBox`
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import init, {set_panic_hook, edit_box} from '/wasm/project.js';
|
import init, { edit_box } from "/wasm/project.js";
|
||||||
|
|
||||||
class OutputBox {
|
window.OutputBox = class {
|
||||||
constructor(element) {
|
constructor(element) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.lastText = null;
|
this.lastText = null;
|
||||||
}
|
}
|
||||||
|
setText(text) {
|
||||||
setText(text) {
|
this.element.innerHTML = text;
|
||||||
this.element.innerHTML = text;
|
}
|
||||||
}
|
get currentText() {
|
||||||
|
return this.element.innerHTML;
|
||||||
get currentText() {
|
}
|
||||||
return this.element.innerHTML;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.OutputBox = OutputBox;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
// Run the init method to initiate the WebAssembly module.
|
|
||||||
await init();
|
|
||||||
set_panic_hook();
|
|
||||||
const wasmoutput = document.querySelector('#wasmoutput');
|
|
||||||
const outputBox = new OutputBox(wasmoutput);
|
|
||||||
const input = document.createElement('input');
|
|
||||||
document.body.appendChild(input);
|
|
||||||
wasmoutput.onclick = () => {
|
|
||||||
const inputValue = input.value;
|
|
||||||
edit_box(outputBox, inputValue);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
It can be imported as such in Rust
|
It can be imported as such in Rust
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub type OutputBox;
|
pub type OutputBox;
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
pub fn new(text: i32) -> OutputBox;
|
pub fn new(element: web_sys::HtmlElement) -> OutputBox;
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
#[wasm_bindgen(method)]
|
||||||
pub fn setText(this: &OutputBox, text: &str);
|
pub fn setText(this: &OutputBox, text: &str);
|
||||||
|
|
||||||
// Has to return owned
|
|
||||||
#[wasm_bindgen(method, getter)]
|
#[wasm_bindgen(method, getter)]
|
||||||
pub fn lastText(this: &OutputBox) -> Option<String>;
|
pub fn lastText(this: &OutputBox) -> Option<String>;
|
||||||
|
|
||||||
#[wasm_bindgen(method, setter)]
|
#[wasm_bindgen(method, setter)]
|
||||||
pub fn set_lastText(this: &OutputBox, text: Option<String>);
|
pub fn set_lastText(this: &OutputBox, text: Option<String>);
|
||||||
|
|
||||||
#[wasm_bindgen(method, getter)]
|
#[wasm_bindgen(method, getter)]
|
||||||
pub fn currentText(this: &OutputBox) -> String;
|
pub fn currentText(this: &OutputBox) -> String;
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
- Getters and Setters have to be declared with an added parameter in the proc macro.
|
||||||
|
- `null` and `undefined` can be both represented by `Option::None`
|
||||||
|
|
||||||
|
Try it in action:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
(async () => {
|
||||||
|
// Run the init method to initiate the WebAssembly module.
|
||||||
|
await init();
|
||||||
|
const wasmoutput = document.querySelector("#wasmoutput");
|
||||||
|
const outputBox = new OutputBox(wasmoutput);
|
||||||
|
const input = document.createElement("input");
|
||||||
|
document.body.appendChild(input);
|
||||||
|
wasmoutput.onclick = () => {
|
||||||
|
const inputValue = input.value;
|
||||||
|
edit_box(outputBox, inputValue);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn edit_box(output_box: &OutputBox, text: &str) {
|
pub fn edit_box(output_box: &OutputBox, text: &str) {
|
||||||
match text {
|
match text {
|
||||||
@ -81,9 +80,4 @@ pub fn edit_box(output_box: &OutputBox, text: &str) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<details>
|
</details>
|
||||||
|
|
||||||
* Getters and Setters have to be declared with an added parameter in the proc macro.
|
|
||||||
* `null` and `undefined` can be both represented by `Option::None`
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
# Import a Javascript method
|
# Import a Javascript method
|
||||||
|
|
||||||
Since Wasm runs in the browser, we will want to interact directly with Javascript APIs from Rust.
|
Methods from javascript can be imported directly as `extern "C"` bindings.
|
||||||
For instance `println!` will not log to the javascript console, so we need to use `console.log`.
|
|
||||||
Similarly, we want to be able to call `alert`. This works the same way as FFIs with C.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn alert(s: &str);
|
fn alert(s: &str);
|
||||||
|
|
||||||
// `js_namespace` will get values inside of a nested object in window. Here, `window.console.log`
|
|
||||||
#[wasm_bindgen(js_namespace = console)]
|
#[wasm_bindgen(js_namespace = console)]
|
||||||
pub fn log(s: &str);
|
pub fn log(s: &str);
|
||||||
|
|
||||||
// jsMethod is a user defined method defined in the `window` object
|
|
||||||
pub fn jsMethod();
|
pub fn jsMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,4 +39,8 @@ window.jsMethod = jsMethod;
|
|||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
|
Since Wasm runs in the browser, we will want to interact directly with Javascript APIs from Rust.
|
||||||
|
For instance `println!` will not log to the javascript console, so we need to use `console.log`.
|
||||||
|
Similarly, we want to be able to call `alert`. This works the same way as FFIs with C.
|
||||||
|
|
||||||
</details>
|
</details>
|
@ -22,18 +22,16 @@ pub fn add_a_cat() -> Result<(), JsValue> {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import init, {set_panic_hook, add_a_cat} from '/wasm/project.js';
|
import init, { add_a_cat } from "/wasm/project.js";
|
||||||
|
|
||||||
|
(async () => {
|
||||||
(async () => {
|
// Run the init method to initiate the WebAssembly module.
|
||||||
// Run the init method to initiate the WebAssembly module.
|
await init();
|
||||||
await init();
|
const button = document.createElement("button");
|
||||||
set_panic_hook();
|
button.textContent = "Add a cat";
|
||||||
const button = document.createElement("button");
|
document.body.appendChild(button);
|
||||||
button.textContent = "Add a cat";
|
button.addEventListener("click", () => {
|
||||||
document.body.appendChild(button);
|
add_a_cat();
|
||||||
button.addEventListener("click", () => {
|
});
|
||||||
add_a_cat();
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
|
@ -1,60 +1,40 @@
|
|||||||
# Borrow Checker
|
# Borrow Checker
|
||||||
|
|
||||||
When we export a Rust type to Javascript and the pass an instance of this type to a method that takes ownership of it, the javascript variable will be cleared and dereferencing it will throw a runtime error.
|
When we export a Rust type to Javascript we need to beware about the borrow checker on the Javascript side.
|
||||||
This essentially implements the borrow checker at Runtime in Javascript.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct MultiCounter {
|
pub struct RustType {}
|
||||||
// We use the counter from the previous slide
|
|
||||||
counters: Vec<Counter>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
impl MultiCounter {
|
impl RustType {
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen]
|
||||||
pub fn new() -> MultiCounter {
|
pub fn new() -> RustType {
|
||||||
MultiCounter { counters: Vec::new() }
|
RustType {}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn increment(&mut self) {
|
|
||||||
for counter in &mut self.counters {
|
|
||||||
counter.increment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_counter(&mut self, counter: Counter) {
|
|
||||||
self.counter.push(counter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn takes_struct(s: RustType) -> RustType {
|
||||||
|
s
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import init, {set_panic_hook, Counter, MultiCounter} from '/wasm/project.js';
|
import init, {RustType, takes_struct} from '/wasm/project.js';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Run the init method to initiate the WebAssembly module.
|
// Run the init method to initiate the WebAssembly module.
|
||||||
await init();
|
await init();
|
||||||
set_panic_hook();
|
const rustType = RustType.new();
|
||||||
const wasmOutput = document.querySelector("#wasmoutput");
|
const moved = takes_struct(rustType);
|
||||||
const button = document.querySelector("#button");
|
console.log(moved);
|
||||||
|
console.log(rustType);
|
||||||
const counter = new Counter("ButtonCounter", 42);
|
|
||||||
counter.increment();
|
|
||||||
// Works fine
|
|
||||||
wasmOutput.textContent = counter.count;
|
|
||||||
|
|
||||||
const multiCounter = new MultiCounter();
|
|
||||||
// Move counter into the MultiCounter
|
|
||||||
multiCounter.add_counter(counter);
|
|
||||||
// Error: Open console
|
|
||||||
counter.increment();
|
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
* `counter` is moved before the second call, so the code panics
|
* `rustType` is moved so it points to null in the second log.
|
||||||
* Ownership rules must be respected
|
* Ownership rules must be respected even in Javascript.
|
||||||
|
* Integral types in JS that are automatically translated to Rust do not have those constraints. `(String, Vec, etc.)`
|
||||||
|
|
||||||
</details>
|
</details>
|
@ -1,20 +1,17 @@
|
|||||||
# Closures
|
# Closures
|
||||||
|
|
||||||
Closures can be returned to Rust and executed on the Wasm runtime.
|
Closures created in Rust have to be returned to so they won't be dropped.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn setInterval(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
|
fn setInterval(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub struct ClosureHandle {
|
pub struct ClosureHandle {
|
||||||
closure: Closure<dyn FnMut()>,
|
closure: Closure<dyn FnMut()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn timeout_set_seconds(elem: web_sys::HtmlElement) -> ClosureHandle {
|
pub fn timeout_set_seconds(elem: web_sys::HtmlElement) -> ClosureHandle {
|
||||||
let seconds = Rc::new(RefCell::new(0usize));
|
let seconds = Rc::new(RefCell::new(0usize));
|
||||||
@ -30,24 +27,23 @@ pub fn timeout_set_seconds(elem: web_sys::HtmlElement) -> ClosureHandle {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
import init, {set_panic_hook, timeout_set_seconds} from '/wasm/project.js';
|
import init, { timeout_set_seconds } from "/wasm/project.js";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Run the init method to initiate the WebAssembly module.
|
// Run the init method to initiate the WebAssembly module.
|
||||||
await init();
|
await init();
|
||||||
const wasmOutput = document.querySelector("#wasmoutput");
|
const wasmOutput = document.querySelector("#wasmoutput");
|
||||||
timeout_set_seconds(wasmOutput);
|
timeout_set_seconds(wasmOutput);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
* Since the function that creates the closure keeps its ownership, the closure would be dropped if we did't return it.
|
- Since the function that creates the closure keeps its ownership, the closure would be dropped if we did't return it.
|
||||||
* Returning ownership allows the JS runtime to manage the lifetime of the closure and to collect it when it can.
|
- Returning ownership allows the JS runtime to manage the lifetime of the closure and to collect it when it can.
|
||||||
* Try returning nothing from the method.
|
- Try returning nothing from the method.
|
||||||
* Closures can only be passed by reference to Wasm functions.
|
- Closures can only be passed by reference to Wasm functions.
|
||||||
* This is why we pass `&Closure` to `setInterval`.
|
- This is why we pass `&Closure` to `setInterval`.
|
||||||
* This is also why we need to create `ClosureHandle` to return the closure.
|
- This is also why we need to create `ClosureHandle` to return the closure.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user