Compilando Rust para WebAssembly e importando via Javascript
Olá Pessoal 🖖 Este artigo tem intuíto de apresentar conceitos iniciais sobre WebAssembly e como utilizar Rust para compilar para binário do WebAssembly. Após compilado o WebAssembly, iremos importá-lo em Javascript.
Iremos escrever a estrutura de dados fila e importá-la em Javascript, no entanto, iremos adotar uma implementação simples, já que o intuito é entender o processo de compilação/importação de WebAssembly. Caso o leitor procure um exemplo mais complexo, tenho essa implementação, epqueue, que inspirou esse post.
O que é e para quê serve WebAssembly?
WebAssembly(Wasm) é um conjunto de especificações que definem um formato de código binário(machine code), semelhante ao Assembly, e também define uma maquina virtual para executar esse código binário. Ambos, maquina virtual e código binário, tem intuíto de serem rápidos e exigirem pouco espaço de armazenamento.
Atualmente, Wasm é suportado pela maiorias do navegadores e muitas linguagem tem ferramentas para compilar código fonte para Wasm. Dessa forma, é possível rodar no browser código escrito em qualquer linguagem, basta compila-la para Wasm e importa-la no navegador.
Nesse artigo, iremos utilizar a linguagem Rust, que é uma linguagem que suporta muito bem Wasm, e compila para um código muito eficiente. No entanto, não será aprofundado em features/sintaxe do Rust, para não confundir o leitor.
Ferramentas necessárias
Será utilizado a ferramenta Cargo
do Rust, para criar a estrutura inicial do nosso projeto e adicionar as dependências necessárias. Para realizar o build do código utilizaremos wasm-pack
e wasm_bindgen
. Além de serem úteis para fazer o build da nossa aplicação, essas ferramentas também são úteis para contornar algumas limitações do WebAssembly.
Setup e estrutura inicial
Para instalar o Cargo
e o compilador Rust recomendo seguir o tutorial da pagina oficial do Rust, Install Rust. De forma simplificada, essa instalação pode ser feita executando o seguinte comando:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Para instalar wasm-pack
, também há um tutorial na pagina oficial, Install wasm-pack. Para usuários de linux, atualmente, recomenda-se executar o comando:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
Após isso, iremos criar a estrutura inicial do nosso exemplo:
# Cria o diretório wasm-demo com estrutura inicial
cargo init --lib wasm-demo
Com estrutura inicial criada, iremos colar a seguinte configuração no arquivo Cargo.toml
, que indica as dependências utilizadas.
[package]
name = "wasm-demo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2.63"
js-sys = "0.3.61"
Com isso, obtemos a seguinte estrutura:
wasm-demo
├── Cargo.toml
└── src
└── lib.rs
Para verificar se tudo foi instalado corretamente, pode se executar o seguinte comando:
wasm-pack build --target nodejs
Ele irá gerar o build da aplicação em Wasm, no diretório pkg
.
Implementação em Rust
Agora iremos implementar o código da nossa fila. O primeiro passo é definirmos em src/lib.rs
uma estrutura que irá representar a fila:
use wasm_bindgen::prelude::JsValue;
pub struct Queue {
vec: Vec<JsValue>
}
A estrutura possui um campo chamado vec
, que armazena um vetor, seus elementos são do tipo JsValue
, que representa um objeto Javascript. Para indicar ao wasm_bindgen
que queremos importar essa estrutura como uma classe JS, iremos adicionar a seguinte macro:
use wasm_bindgen::prelude::{JsValue, wasm_bindgen};
#[wasm_bindgen]
pub struct Queue {
vec: Vec<JsValue>
}
Agora iremos implementar o construtor da nossa estrutura:
// trecho anterior omitido
impl Queue {
pub fn new() -> Queue {
Queue {
vec: Vec::new()
}
}
}
Isto indica ao Rust como criar uma instância da estrutura, para indicar que essa estrutura pode ser construída via Javascript, fazemos a seguinte alteração:
#[wasm_bindgen]
impl Queue {
#[wasm_bindgen(constructor)]
pub fn new() -> Queue {
Queue {
vec: Vec::new()
}
}
}
Com a notação wasm_bindgen(constructor)
, indicamos que Queue
pode ser criada em Javascript utilizando a keyword new
. Por fim, iremos implementar os métodos push
e pop
da fila:
#[wasm_bindgen]
impl Queue {
// trecho anterior omitido
pub fn push(&mut self, element: JsValue) {
self.vec.push(element);
}
pub fn pop(&mut self) -> JsValue {
self.vec.remove(0)
}
}
Finalizamos nossa implementação, após essas adições o código do exemplo se encontra neste gist.
Compilando o código para WebAssembly
Finalizado a implementação, iremos compilar nosso código para WebAssembly e gerar um pacote Javascript. Para isso, execute o comando:
wasm-pack build --target nodejs
Com isso, é criado o diretório pkg
, que contém a resultado do processo de build.
pkg
├── package.json
├── wasm_demo_bg.wasm
├── wasm_demo_bg.wasm.d.ts
├── wasm_demo.d.ts
└── wasm_demo.js
O arquivo wasm_demo_bg.wasm
, é o código binário Wasm. Enquanto o arquivo wasm_demo.js
é responsável por importar o binário e expor a estrutura que definimos em Rust, Queue
.
Agora, podemos importar em Javascript e utilizar nosso pacote Wasm. Para importar e usar o pacote, basta no diretório wasm-demo
, abrir o REPL do node, digitando node
, e executar os comandos do exemplo a seguir:
// Importando pacote e criando nova fila
const {Queue} = require('./pkg');
let queue = new Queue();
// Inserindo elementos
queue.push('primeiro elemento');
queue.push(['segundo elemento', 'do tipo array']);
queue.push({'msg': 'terceiro elemento do tipo JSON'});
// Imprimindo elementos
console.log(queue.pop()); // imprime: primeiro elemento
console.log(queue.pop()); // imprime: [ 'segundo elemento', 'do tipo array' ]
console.log(queue.pop()); // imprime: { msg: 'terceiro elemento do tipo JSON' }
Como o diretório pkg
trata-se de um pacote JS, ele pode ser utilizado não somente como citado anteriormente, mas também pode ser publicado no npm ou instalado em outro projeto local, usando npm install ./pkg
.
Referências
Espero que o artigo tenha causado interesse no leitor sobre o tema. Caso sim, deixo aqui uma lista de referências para aprofundar o assunto:
- Repositório com implementação
production-ready
- Ebook sobre uso de Rust e WebAssembly
- Guia sobre
wasm_bindgen
- Ebook: The Rust Programming Language
Dúvidas e sugestões são bem-vindas. Obrigado, aos leitores 🙏