Einleitung: Warum Rust und WebAssembly auf fortgeschrittenem Niveau
\nWebAssembly (Wasm) und Rust sind eine Kombination, die es ermöglicht, rechenintensive Aufgaben aus JavaScript in hochperformanten nativen Code zu verlagern, der im Browser läuft. Auf grundlegendem Niveau haben Sie sich vielleicht bereits mit der Kompilierung einfacher Funktionen vertraut gemacht. Für die Erstellung echter Produkte – Spiel-Engines, Bildverarbeitungsprogramme, Parser oder kryptografische Bibliotheken – ist jedoch ein tiefes Verständnis der Speicherverwaltung, der Übergabe komplexer Datenstrukturen und der Interaktion mit der externen Umgebung erforderlich.
\nIn diesem Artikel betrachten wir fortgeschrittene Techniken der Arbeit mit WebAssembly in Rust: von der manuellen Speicherverwaltung bis zur Erstellung hybrider Anwendungen, bei denen Rust und JavaScript als eine Einheit arbeiten. Das Material richtet sich an Entwickler, die bereits mit den Grundlagen von Rust und Wasm vertraut sind.
\n\n1. Speicherverwaltung: linearer Speicher und Allokatoren
\nIm Gegensatz zu JavaScript verwendet WebAssembly linearen Speicher – ein flaches Array von Bytes. Rust-Code, der in Wasm kompiliert wurde, arbeitet direkt mit diesem Speicher. Für effizientes Arbeiten ist es notwendig zu verstehen, wie man Speicher manuell zuweist und freigibt.
\n\n1.1. Manuelle Speicherzuweisung mit wasm_bindgen
\nDie Bibliothek wasm-bindgen bietet praktische Abstraktionen, aber auf fortgeschrittenem Niveau ist oft ein direkter Zugriff auf den Speicher erforderlich. Betrachten wir ein Beispiel:
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); // verhindert das Löschen des Vektors\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}\nHier weisen wir Speicher über Vec zu, "vergessen" ihn (damit Rust den Puffer nicht löscht) und übergeben den Zeiger an JavaScript. Die Funktion deallocate_buffer stellt den Vektor wieder her und gibt den Speicher korrekt frei.
1.2. Verwendung eines benutzerdefinierten Allokators
\nFür komplexe Projekte lohnt es sich, den Standard-Allokator durch einen vorhersagbareren zu ersetzen. Zum Beispiel wee_alloc – ein kleiner, für Wasm optimierter Allokator:
// in Cargo.toml\n// [dependencies]\n// wee_alloc = "0.4"\n\n#[global_allocator]\nstatic ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;\nDies reduziert die Größe der resultierenden .wasm-Datei und beschleunigt die Speicherzuweisung im Browser.
\n\n2. Übergabe komplexer Datenstrukturen zwischen Rust und JavaScript
\nAuf fortgeschrittenem Niveau sind Sie nicht auf Zahlen und Zeichenketten beschränkt. Es ist notwendig, Arrays, Strukturen und sogar Closures zu übergeben.
\n\n2.1. Strukturen mit #[wasm_bindgen]
\nSie können ganze Rust-Strukturen als JavaScript-Klassen exportieren:
\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("Inkompatible Dimensionen".to_string());\n }\n // ... Implementierung der Matrizenmultiplikation\n let result = Matrix::new(self.rows, other.cols);\n Ok(result)\n