Hochperformante Webanwendungen Web-Apps mit WebAssembly und Rust

Von Thomas Joos 7 min Lesedauer

Rust und WebAssembly, kurz Wasm, ermöglichen hohe Geschwindigkeiten und eine sichere Ausführung von Webanwendungen. In diesem Beitrag zeigen wir, auf was Entwickler achten sollten, um den Wasm-Standard und die Programmiersprache optimal zusammen einzusetzen.

Entwicklung moderner Webanwendungen mit Webassembly und Rust.(Bild:  © greenbutterfly - stock.adobe.com)
Entwicklung moderner Webanwendungen mit Webassembly und Rust.
(Bild: © greenbutterfly - stock.adobe.com)

High-Performance-Webanwendungen sind in modernen Anwendungsfällen essentiell, um eine hohe Benutzerzufriedenheit und effiziente Ressourcennutzung zu gewährleisten. WebAssembly (im Folgenden Webassembly oder Wasm) und Rust bieten zusammen eine leistungsfähige Lösung, um hohe Geschwindigkeiten im Web zu erreichen.

Webassembly ist ein binäres Anweisungsformat, das von Webbrowsern unterstützt wird und eine nahezu native Ausführungsgeschwindigkeit ermöglicht. Es dient als portable Kompilierungsziel für Sprachen wie C, C++ und Rust. Wasm wurde entwickelt, um die Performance-Limitierungen von JavaScript zu überwinden, und bietet eine sicherere und effizientere Ausführungsumgebung. Es arbeitet eng mit bestehenden Webtechnologien zusammen und ermöglicht die nahtlose Integration in moderne Webanwendungen.

Rust vereint Effizienz und Sicherheit

Rust ist eine speichersichere Programmiersprache, die hohe Leistung und einfache Handhabung vereint. Sie ist besonders geeignet für Systeme, bei denen Performance entscheidend ist, wie Spiel-Engines, Datenbanken oder Betriebssysteme, und eignet sich hervorragend für Webassembly. Rust wurde 2007 von Graydon Hoare als Nebenprojekt gestartet und 2009 von Mozilla unterstützt.

Im Gegensatz zu traditionellen Sprachen, die einen Garbage Collector nutzen, und zu Sprachen, die direkte Speicherverwaltung erlauben, erreicht Rust Speichersicherheit durch das Konzept von Ownership und Borrowing. Variablen in Rust sind standardmäßig unveränderlich und nutzen Stack-Speicher für minimale Performance-Overheads. Mutable Werte und Objekte unbekannter Größe zur Kompilierzeit werden im Heap-Speicher verwaltet.

Jede Variable hat einen Owner, und wenn diese Variable aus dem Gültigkeitsbereich fällt, wird der zugehörige Speicher automatisch freigegeben. Referenzen können geliehen werden, ohne Ownership zu übernehmen, was durch den Borrow Checker zur Kompilierzeit überprüft wird, um Speicherfehler zu vermeiden. Rust verfügt über den Paketmanager Cargo, der die Erstellung neuer Projekte erleichtert.

Die Wahrheit über die Leistung von Rust und Webassembly

Webassembly und Rust bieten leistungsstarke Möglichkeiten für die Entwicklung moderner Webanwendungen. Ein häufiges Missverständnis ist, dass Webassembly immer schneller als JavaScript ist. Tatsächlich erreicht JavaScript durch Just-in-Time-Compilation oft vergleichbare Geschwindigkeiten. Webassembly kann bestimmte Aufgaben schneller erledigen, jedoch ist die Browserleistung ein limitierender Faktor.

Ein weiterer Mythos ist, dass Webassembly zu langsam sei. Einige Rust-Frameworks, die Webassembly nutzen, sind in Benchmarks schneller als viele JavaScript-Frameworks. Zum Beispiel zeigen die Ergebnisse des JS-Framework-Benchmarks, dass Rust-Webassembly-Frameworks wie Leptos und Dioxus konkurrenzfähig sind, oft sogar schneller als Frameworks wie React oder Svelte.

Webassembly kann derzeit nicht direkt auf DOM-APIs zugreifen, sondern muss über JavaScript kommunizieren. Diese Einschränkung wird jedoch durch Optimierungen in Frameworks wie Dioxus, die statische und dynamische Teile eines Templates unterscheiden, minimiert. Diese Optimierungen ermöglichen feinere und effizientere Updates. Insgesamt zeigen Benchmarks, dass Rust und Webassembly für Frontend-Rendering schnell genug sind. Größere Anwendungen könnten jedoch durch die größere Größe der Webassembly-Binaries und das Fehlen von Code-Splitting beeinträchtigt werden. Die Kompilierungsgeschwindigkeit von Webassembly und die Effizienz bei der Speicherverwaltung gleichen diese Nachteile oft aus.

Webassembly ist bereit für den Einsatz in High-Performance-Webanwendungen, insbesondere wenn feinkörnige Reaktivität und optimierte Rendering-Techniken verwendet werden. Entwickler sollten jedoch die spezifischen Anforderungen ihrer Anwendungen berücksichtigen, bevor sie sich für eine Technologie entscheiden.

Rust für Webanwendungen nutzen

Im Vergleich zu anderen Sprachen bietet Rust eine strikte Kompilierungszeit-Garantie zur Vermeidung von Speicherfehlern und Datenrennen. Rust kombiniert die Performance von Sprachen wie C und C++ mit einer modernen Syntax und einem starken Typsystem, wodurch es sich ideal für die Entwicklung sicherer und effizienter Webanwendungen eignet.

Jetzt Newsletter abonnieren

Täglich die wichtigsten Infos zu RZ- und Server-Technik

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung. Die Einwilligungserklärung bezieht sich u. a. auf die Zusendung von redaktionellen Newslettern per E-Mail und auf den Datenabgleich zu Marketingzwecken mit ausgewählten Werbepartnern (z. B. LinkedIn, Google, Meta).

Aufklappen für Details zu Ihrer Einwilligung

Die Installation von Rust erfolgt über den offiziellen Installer rustup, der durch die Eingabe von …

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

… im Terminal ausgeführt wird. Um Rust für Webassembly zu konfigurieren, werden Tools wie wasm-pack und cargo-web benötigt. Diese lassen sich mit den Befehlen …

cargo install wasm-pack

… und …

cargo install cargo-web

… installieren. Das Build-System wird durch die Konfiguration der Cargo.toml-Datei und das Hinzufügen der entsprechenden Zielplattform (wasm32-unknown-unknown) angepasst.

Erstellen eines einfachen Rust-Projekts für Webassembly

Um ein Rust-Projekt für Webassembly zu erstellen, muss zunächst die Entwicklungsumgebung mit den notwendigen Rust-Erweiterungen eingerichtet werden. Anschließend wird die Projektstruktur mit einer Cargo.toml-Datei definiert, in der die Abhängigkeiten wie wasm-bindgen festgelegt werden. Rust-Code kann dann mithilfe von wasm-bindgen und Attributen wie #[wasm_bindgen] in Webassembly kompiliert werden.

Ein neues Rust-Projekt wird durch den Befehl cargo new --lib projektname initialisiert. Die Projektstruktur umfasst eine src-Verzeichnis mit der lib.rs als Haupteinstiegsdatei. Eine einfache Funktion in Rust könnte wie folgt aussehen:

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
   a + b
}

Diese Funktion addiert zwei Zahlen und wird später in Webassembly kompiliert. Die Kompilierung erfolgt mit wasm-pack durch den Befehl wasm-pack build, der das Rust-Projekt in ein Webassembly-Modul umwandelt. Alternativ können cargo-web oder direkt cargo mit den entsprechenden Zielplattformen genutzt werden. Debugging-Tools wie wasm-bindgen helfen bei der Fehlersuche und -behebung, indem sie die Interoperabilität zwischen Webassembly und JavaScript verbessern.

Durch die Kompilierung auf Wasm können Anwendungen plattformübergreifend funktionieren, ohne dass Nutzer zusätzliche Software installieren müssen. Die Unterstützung verschiedener Programmiersprachen erfolgt durch die Implementierung eines Wasm-Compilers für jede Sprache. Zum Beispiel kann Rust durch das Tool wasm-pack in Webassembly kompiliert werden, was die Erstellung von Webanwendungen erleichtert.

Integration von Webassembly in eine Webanwendung

Die Integration von Webassembly-Modulen in JavaScript erfolgt durch das Laden des Moduls mittels Fetch-API und anschließendes Instanziieren:

fetch('module.wasm').then(response =>
   response.arrayBuffer()
).then(bytes =>
   Webassembly.instantiate(bytes)
).then(results => {
   console.log(results.instance.exports.add(2, 3));
});

Dieser Code lädt ein Webassembly-Modul und ruft die Funktion add auf, die im Rust-Projekt definiert wurde. Code-Optimierungen in Rust können durch effiziente Algorithmen und Datenstrukturen erreicht werden. Rust-Crates wie wee_alloc helfen, den Speicherverbrauch zu minimieren. Für die Optimierung der Webassembly-Module kann wasm-opt verwendet werden, um die Modulgröße zu reduzieren und die Ausführungsgeschwindigkeit zu verbessern.

Toolchains und Werkzeuge

Wichtige Tools für die Entwicklung mit Webassembly und Rust sind wasm-bindgen, das die Interaktion zwischen Rust und JavaScript erleichtert, sowie wasm-opt zur Optimierung der Webassembly-Module. Profiling-Tools wie wasm-gc helfen, unnötigen Code zu entfernen. Continuous Integration (CI) kann durch Systeme wie GitHub Actions oder Travis CI implementiert werden, um den Build- und Testprozess zu automatisieren.

Anwendungsbeispiele für Rust und Webassembly in Web- und Cloud-Anwendungen

Rust und Webassembly können zur schnellen Bildverarbeitung im Browser genutzt werden:

use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn grayscale(data: &mut [u8]) {
   for pixel in data.chunks_exact_mut(4) {
      let gray = (0.3 * pixel[0] as f32 + 0.59 * pixel[1] as f32 + 0.11 * pixel[2] as f32) as u8;
      pixel[0] = gray;
      pixel[1] = gray;
      pixel[2] = gray;
   }
}

Hier die JavaScript-Integration:

import { grayscale } from './pkg/your_wasm_package.js';
const img = document.getElementById('image');
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, img.width, img.height);
grayscale(new Uint8Array(imageData.data.buffer));
ctx.putImageData(imageData, 0, 0);

Beispiel 2: Schnelle JSON-Verarbeitung in einer Webanwendung

Rust kann auch zur effizienten Verarbeitung großer JSON-Datenmengen genutzt werden:

use wasm_bindgen::prelude::*;
use serde_json::Value;
#[wasm_bindgen]
pub fn parse_json(input: &str) -> String {
   let v: Value = serde_json::from_str(input).unwrap();
   v["name"].to_string()
}

Hier wiederum die JavaScript-Integration:

import { parse_json } from './pkg/your_wasm_package.js';
const jsonString = '{"name": "John", "age": 30}';
const name = parse_json(jsonString);
console.log(name); // Outputs: "John"

Beispiel 3: Optimierte Textverarbeitung in einer Cloudanwendung

Rust und Webassembly können zur Optimierung von Textverarbeitungsaufgaben in Cloudanwendungen verwendet werden:use wasm_bindgen::prelude::*;
use regex::Regex;
#[wasm_bindgen]
pub fn count_words(text: &str) -> usize {
   let re = Regex::new(r"\w+").unwrap();
   re.find_iter(text).count()
}

JavaScript-Integration:

import { count_words } from './pkg/your_wasm_package.js';
const text = "Rust and Webassembly enable high-performance web applications.";
const wordCount = count_words(text);
console.log(wordCount); // Outputs: 7

Beispiel 4: Datenkompression in einer Cloudanwendung

Rust kann für die effiziente Kompression und Dekompression von Daten genutzt werden.

use wasm_bindgen::prelude::*;
use flate2::{write::ZlibEncoder, Compression};
use std::io::prelude::*;
#[wasm_bindgen]
pub fn compress(data: &[u8]) -> Vec<u8> {
   let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
   encoder.write_all(data).unwrap();
   encoder.finish().unwrap()
}

JavaScript-Integration:

import { compress } from './pkg/your_wasm_package.js';
const data = new TextEncoder().encode("Hello, Rust and Webassembly!");
const compressedData = compress(data);
console.log(compressedData); // Outputs compressed data as Uint8Array

Alternativen zu Rust und Webassembly

Obwohl Rust und Webassembly leistungsstarke Werkzeuge für die Entwicklung von High-Performance-Webanwendungen sind, gibt es Alternativen, die in bestimmten Szenarien besser geeignet sein können. Eine Alternative ist TypeScript, das auf JavaScript aufbaut und durch Typensicherheit und bessere Entwicklungswerkzeuge die Produktivität erhöht. TypeScript eignet sich besonders für Anwendungen, die stark auf JavaScript und das DOM angewiesen sind.

Python, mit Frameworks wie Django oder Flask, ist eine weitere Alternative, die besonders in der Backend-Entwicklung und bei datenintensiven Anwendungen beliebt ist. Für Webanwendungen, die keine extremen Leistungsanforderungen haben, bietet Python eine schnelle Entwicklung und umfangreiche Bibliotheken. Go, eine von Google entwickelte Sprache, eignet sich hervorragend für Cloud-Anwendungen und Microservices. Es bietet einfache Concurrency-Mechanismen und ist bekannt für seine effiziente Speicherverwaltung.

Rust und Webassembly sind weniger geeignet für einfache Webanwendungen, bei denen die Entwicklungsgeschwindigkeit und -flexibilität wichtiger sind als maximale Performance. In solchen Fällen sind TypeScript oder sogar klassische JavaScript-Frameworks wie React oder Vue oft die bessere Wahl. Rust und Webassembly können zudem überdimensioniert sein für Anwendungen, die hauptsächlich aus statischen Inhalten bestehen oder nur minimale Interaktivität erfordern. Hier sind leichtgewichtige Lösungen wie HTML5 und CSS völlig ausreichend.

(ID:50113318)