Si eres desarrollador de JavaScript en este tutorial vamos a ver algunos ejemplos básicos de cómo puedes ir empezando a usar WebAssembly escrito en Rust en tus aplicaciones JS.
Instalación (unix)
Lo primero que vamos hacer es instalar rustup
que es el instalador oficial de Rust. Esto nos permitirá cambiar entre versiones de forma fácil.
1 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
Ahora instalamos wasm-pack
. Con esta biblioteca podemos convertir nuestro código de Rust a WebAssembly fácilmente.
1 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh |
“Hola Mundo” en Rust
A continuación, creamos la siguiente estructura de archivos:
1 | ├── Cargo.toml |
Cargo.toml
Cargo.toml
es el equivalente al típico package.json
. Indicamos la información básica en nuestra biblioteca de Rust y añadimos como dependencia wasm-bindgen
que nos ayudará a comunicar JavaScript y Rust.
Con wasm-bindgen
importamos a Rust la manipulación del DOM o logging propias de JS y exportamos a JS la funcionalidad de Rust. Además, si usamos Typescript wasm-bindgen
nos generará los .d.ts
.
Por último, añadimos crate-type = ["cdylib"]
, esto le dice a Rust que al hacer build haga una versión en cdylib
de nuestro paquete, o sea, que genere los .so, .dll.
1 | [package] |
src/lib.rs
En lib.rs
vamos a meter todo el código de ejemplo en Rust. Si no has programado antes en Rust puedes consultar el libro gratuito. The Rust Programming Language. Vamos a intentar que los ejemplos sean lo más sencillos posible para que puedas desenvolverte lo suficiente sin entender del todo Rust.
En este código empezamos a usar wasm-bindgen
para comunicarnos con JS. En extern
estamos diciéndole a Rust que vamos a ejecutar una función definida en otro módulo, wasm-bindgen se encargará de facilitar el alert
de JS. Y en pub fn greet
estamos creando una función pública que podremos ejecutar desde JS y que lanzará el alert
con una cadena de texto.
1 | use wasm_bindgen::prelude::*; |
Ahora si todo es correcto podemos ejecutar wasm-pack build
y la primera vez veremos algo como esto:
Al terminar, en el directorio aparecerán los siguientes archivos:
hello_rust_bg.wasm
Este archivo contiene el binario generado por Rust de nuestro código en ´lib.rs`.
hello_rust.js
Este archivo es generado por wasm-bindgen
y actúa como puente entre el binario y JS. Se encarga de enviarle al binario las funciones de JS que pueda necesitar y la conversión de tipos si es necesaria. Echale un vistazo porque es bastante interesante.
hello_rust.d.ts
Contiene la declaración de tipos de Typescript.
package.json
Información necesaria si queremos publicar como biblioteca.
“Hola Mundo” en JS -> Rust
Ahora en la raíz de nuestro proyecto vamos a crear más archivos para que nos quede la siguiente estructura:
1 | ├── pkg |
package.json
En este package.json
hemos instalado varias dependencias como webpack
y webpack-dev-server
para gestionar los módulos y servir nuestro ejemplo. En dependencies
creamos el módulo wasm
que apunta a la carpeta pkg
creada en el build anterior.
1 | { |
index.html
Creamos un html básico que simplemente llama a init.js y que se encargará de hacer el bootstrap del ejemplo.
1 |
|
init.js
Aquí simplemente importamos el módulo main.js
donde estará la lógica principal de JS.
1 | import('./main.js') |
webpack.config.js
Esta es la configuración básica de webpack. Es importante indicar en el entry el mismo fichero que en nuestro index.html.
1 | module.exports = { |
main.js
1 | import * as wasm from 'wasm'; |
Importamos wasm
tal como hemos indicado en el package.json
y llamamos a la función definida en Rust de greet
.
Ya tenemos todo listo, ahora ejecutamos npx webpack-dev-server
y vamos a http://localhost:8080/
No es muy impresionante, pero como ves la comunicación es muy sencilla.
Si queremos simplificar el proceso podemos instalar el plugin de webpack wasm-pack-plugin para no tener que hacer wasm-pack build
por cada cambio en Rust.
Rendimiento
Una de las ventajas de Rust vs JS es el rendimiento, aunque este no es un ejemplo muy realista por su sencillez y exigencia, vamos a ejecutar Fibonacci de forma recursiva en JS y Rust para ver las diferencias.
Primero añadimos Fibonacci como una nueva función pública en src/lib.rs
y volvemos a lanzar wasm-pack build
.
1 |
|
En main.js
escribimos la misma función de Fibonacci en JS. A continuación, vamos a medir el tiempo de ejecución en Rust y JS con índice 40.
1 | import * as wasm from "wasm"; |
1 | main.js:18 FibonacciRust: 489.605712890625ms |
Como te habrás dado cuenta JS tarda el doble que Rust en hacer la misma operación.
DOM
Con wasm-bindgen
también podemos manipular el DOM desde Rust, pero necesitamos una nueva la biblioteca, web-sys
. Primero la añadimos en el Cargo.toml
con las features que queramos.
1 | [dependencies.web-sys] |
Editamos src/lib.rs
y añadimos la siguiente función:
1 |
|
Aquí tenemos unas cuantas cosas nuevas que si no conoces Rust pueden confundirte, así que vamos a repasar lo básico.
#[wasm_bindgen(start)]
: Con esto wasm_bindgen
ejecutará esta función en cuanto sea importando, no vamos a tener que llamar manualmente a init
.
web_sys::window().expect("window not found")
: Estamos pidiendo a web_sys el objeto window de JS y el expect es el mensaje de error si no lo encuentra.
?
: La interrogación es para hacer el control de errores más fácil, es un shortcut de este código.
1 | let val = match document.create_element("h1") { |
&
: Mandamos a apend_child
la referencia al objeto que hay en val
.
Ok(())
: Se espera que la función init
devuelva un resultado o un error. Con Ok
estamos diciendo que todo ha ido bien, un resultado vacío.
Si volvemos hacer wasm-pack build
podemos ver que al cargar tenemos h1 creado en Rust.
Conclusión
Hemos podido hacer lo básico para poder ejecutar WebAssemby, escrito en Rust, en JavaScript. Si te interesa seguir aprendiendo aquí tienes los siguientes enlaces. Espero que te haya sido útil.
https://doc.rust-lang.org/book/
https://rustwasm.github.io/docs/wasm-bindgen/
https://rustwasm.github.io/docs/book/
https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm