Rust para WebAssembly: wasm-pack, wasm-bindgen y llamar a Rust desde JavaScript

Rust compila a WebAssembly con rendimiento cercano al nativo. wasm-pack automatiza el proceso de compilación y empaquetado; wasm-bindgen genera los bindings entre Rust y JavaScript. El resultado es un módulo que puedes importar desde cualquier bundler moderno o directamente desde el navegador.

Configuración inicial

// Instalar las herramientas:
// cargo install wasm-pack
// cargo install wasm-bindgen-cli  (opcional, wasm-pack lo gestiona)

// Cargo.toml
[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

// Para acceder a APIs del navegador:
// [dependencies]
// web-sys = { version = "0.3", features = ["Window", "Document", "Element"] }
// js-sys  = "0.3"

Exportar funciones a JavaScript

use wasm_bindgen::prelude::*;

// #[wasm_bindgen] hace la función visible desde JS
#[wasm_bindgen]
pub fn saludar(nombre: &str) -> String {
    format!("Hola, {nombre}! Desde Rust/WebAssembly")
}

#[wasm_bindgen]
pub fn suma(a: f64, b: f64) -> f64 {
    a + b
}

// Structs exportadas se convierten en clases JS
#[wasm_bindgen]
pub struct Punto {
    x: f64,
    y: f64,
}

#[wasm_bindgen]
impl Punto {
    #[wasm_bindgen(constructor)]
    pub fn new(x: f64, y: f64) -> Punto { Punto { x, y } }

    pub fn distancia(&self, otro: &Punto) -> f64 {
        let dx = self.x - otro.x;
        let dy = self.y - otro.y;
        (dx * dx + dy * dy).sqrt()
    }

    pub fn x(&self) -> f64 { self.x }
    pub fn y(&self) -> f64 { self.y }
}

Compilar y usar desde JavaScript

// Compilar:
// wasm-pack build --target web

// El resultado en pkg/:
// mi_crate.js       ? módulo JS con los bindings
// mi_crate_bg.wasm  ? el binario WebAssembly
// mi_crate.d.ts     ? tipos TypeScript generados automáticamente

// index.html / index.js:
// import init, { saludar, suma, Punto } from './pkg/mi_crate.js';
//
// async function main() {
//   await init();  // carga el .wasm
//
//   console.log(saludar("mundo"));    // "Hola, mundo! Desde Rust/WebAssembly"
//   console.log(suma(3.14, 2.86));    // 6
//
//   const p1 = new Punto(0, 0);
//   const p2 = new Punto(3, 4);
//   console.log(p1.distancia(p2));    // 5
// }
// main();

Llamar a APIs del navegador con web-sys

use wasm_bindgen::prelude::*;
use web_sys::{window, HtmlElement};

#[wasm_bindgen]
pub fn cambiar_titulo(nuevo_titulo: &str) {
    let win = window().unwrap();
    let doc = win.document().unwrap();
    doc.set_title(nuevo_titulo);

    if let Some(h1) = doc.get_element_by_id("titulo") {
        let el: HtmlElement = h1.dyn_into().unwrap();
        el.set_inner_text(nuevo_titulo);
    }
}

#[wasm_bindgen]
pub fn log(msg: &str) {
    web_sys::console::log_1(&JsValue::from_str(msg));
}

Cálculo pesado: CRC32

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn crc32(data: &[u8]) -> u32 {
    let mut crc: u32 = 0xFFFF_FFFF;
    for &byte in data {
        crc ^= byte as u32;
        for _ in 0..8 {
            if crc & 1 == 1 {
                crc = (crc >> 1) ^ 0xEDB8_8320;
            } else {
                crc >>= 1;
            }
        }
    }
    !crc
}

// Desde JS:
// const encoder = new TextEncoder();
// const bytes = encoder.encode("hello world");
// console.log(crc32(bytes).toString(16)); // 0d4a1185

Pasar arrays JS nativos

use wasm_bindgen::prelude::*;
use js_sys::Float64Array;

#[wasm_bindgen]
pub fn estadisticas(datos: &[f64]) -> Float64Array {
    let n = datos.len() as f64;
    let media = datos.iter().sum::<f64>() / n;
    let varianza = datos.iter()
        .map(|x| (x - media).powi(2))
        .sum::<f64>() / n;
    let desviacion = varianza.sqrt();
    let min = datos.iter().cloned().fold(f64::INFINITY, f64::min);
    let max = datos.iter().cloned().fold(f64::NEG_INFINITY, f64::max);

    Float64Array::from(&[media, varianza, desviacion, min, max][..])
}

// Desde JS:
// const stats = estadisticas(new Float64Array([1,2,3,4,5]));
// console.log('Media:', stats[0], 'Desv:', stats[2]);

Resumen

  • wasm-pack build --target web compila y genera los bindings JS listos para importar.
  • #[wasm_bindgen] en funciones y structs las expone a JavaScript con tipos automáticamente convertidos.
  • web-sys da acceso a todas las Web APIs del navegador; activa solo los features que necesitas.
  • js-sys provee tipos JS nativos como Float64Array, Promise o Object.
  • wasm-bindgen genera tipos TypeScript (.d.ts) sin ninguna configuración adicional.
  • El punto fuerte de Rust/WASM: cálculo intensivo (criptografía, compresión, procesamiento de imágenes) sin tocar el main thread de JavaScript.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP