Introducción: por qué Rust y WebAssembly a nivel avanzado
\nWebAssembly (Wasm) y Rust son una combinación que permite trasladar tareas computacionalmente pesadas desde JavaScript a código nativo de alto rendimiento que se ejecuta en el navegador. A nivel básico, es posible que ya te hayas familiarizado con la compilación de funciones simples. Sin embargo, para crear productos reales —motores de juegos, procesadores de imágenes, analizadores sintácticos o bibliotecas criptográficas— se requiere una comprensión profunda de la gestión de memoria, la transferencia de estructuras de datos complejas y la interacción con el entorno externo.
\nEn este artículo, exploraremos técnicas avanzadas para trabajar con WebAssembly en Rust: desde la gestión manual de memoria hasta la creación de aplicaciones híbridas donde Rust y JavaScript funcionan como una sola unidad. El material está dirigido a desarrolladores que ya conocen los fundamentos de Rust y Wasm.
\n\n1. Gestión de memoria: memoria lineal y asignadores
\nA diferencia de JavaScript, WebAssembly utiliza memoria lineal — un arreglo plano de bytes. El código Rust compilado a Wasm trabaja directamente con esta memoria. Para un trabajo eficiente, es necesario comprender cómo asignar y liberar memoria manualmente.
\n\n1.1. Asignación manual de memoria con wasm_bindgen
\nLa biblioteca wasm-bindgen proporciona abstracciones convenientes, pero a nivel avanzado a menudo se requiere acceso directo a la memoria. Veamos un ejemplo:
use wasm_bindgen::prelude::*;\nuse std::mem;\nuse std::slice;\n\n#[wasm_bindgen]\npub fn allocate_buffer(size: usize) -> *mut u8 {\n let mut buf = Vec::with_capacity(size);\n let ptr = buf.as_mut_ptr();\n mem::forget(buf); // evitamos la eliminación del vector\n ptr\n}\n\n#[wasm_bindgen]\npub fn deallocate_buffer(ptr: *mut u8, size: usize) {\n unsafe {\n let _ = Vec::from_raw_parts(ptr, 0, size);\n }\n}\n\n#[wasm_bindgen]\npub fn fill_buffer(ptr: *mut u8, value: u8, len: usize) {\n let slice = unsafe { slice::from_raw_parts_mut(ptr, len) };\n for byte in slice.iter_mut() {\n *byte = value;\n }\n}\nAquí asignamos memoria a través de Vec, "olvidamos" el vector (para que Rust no elimine el búfer) y pasamos el puntero a JavaScript. La función deallocate_buffer restaura el vector y libera la memoria correctamente.
1.2. Uso de un asignador personalizado
\nPara proyectos complejos, vale la pena reemplazar el asignador estándar por uno más predecible. Por ejemplo, wee_alloc es un asignador pequeño optimizado para Wasm:
// en Cargo.toml\n// [dependencies]\n// wee_alloc = "0.4"\n\n#[global_allocator]\nstatic ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;\nEsto reducirá el tamaño del archivo .wasm final y acelerará la asignación de memoria en el navegador.
\n\n2. Transferencia de estructuras de datos complejas entre Rust y JavaScript
\nA nivel avanzado, no te limitas a números y cadenas. Es necesario transferir arreglos, estructuras e incluso clausuras.
\n\n2.1. Estructuras con #[wasm_bindgen]
\nPuedes exportar estructuras Rust completas como clases de JavaScript:
\n#[wasm_bindgen]\npub struct Matrix {\n rows: usize,\n cols: usize,\n data: Vec<f64>,\n}\n\n#[wasm_bindgen]\nimpl Matrix {\n #[wasm_bindgen(constructor)]\n pub fn new(rows: usize, cols: usize) -> Matrix {\n Matrix {\n rows,\n cols,\n data: vec![0.0; rows * cols],\n }\n }\n\n pub fn get(&self, row: usize, col: usize) -> f64 {\n self.data[row * self.cols + col]\n }\n\n pub fn set(&mut self, row: usize, col: usize, val: f64) {\n self.data[row * self.cols + col] = val;\n }\n\n pub fn multiply(&self, other: &Matrix) -> Result<Matrix, String> {\n if self.cols != other.rows {\n return Err("Dimensiones incompatibles".to_string());\n }\n // ... implementación de multiplicación de matrices\n let result = Matrix::new(self.rows, other.cols);\n Ok(result)\n