Введение в WebAssembly и Rust
WebAssembly (WASM) — это низкоуровневый бинарный формат инструкций, который позволяет запускать высокопроизводительный код в браузере почти с нативной скоростью. Rust, с его системой владения и отсутствием сборщика мусора, является идеальным языком для компиляции в WebAssembly. В этой статье мы пройдем путь от установки инструментов до создания полноценного WASM-модуля на Rust.
Почему именно Rust? Он предоставляет безопасную работу с памятью, минимальный размер бинарника и отличную производительность. В сочетании с WebAssembly вы получаете возможность писать сложные алгоритмы, игры, графические редакторы и даже полноценные приложения, работающие в браузере.
Установка инструментов и подготовка окружения
Для начала работы вам понадобятся:
- Rust (установленный через
rustup) - Целевая платформа
wasm32-unknown-unknown - Инструмент
wasm-pack(сборка и публикация WASM) - Базовый HTTP-сервер (например,
python3 -m http.server)
Установка целевой платформы и wasm-pack:
# Установка целевой платформы для WebAssembly
rustup target add wasm32-unknown-unknown
# Установка wasm-pack
cargo install wasm-pack
Теперь создадим новый проект:
cargo new --lib wasm-rust-demo
cd wasm-rust-demo
Откройте Cargo.toml и добавьте зависимость:
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
Флаг cdylib указывает, что мы создаем динамическую библиотеку, совместимую с WebAssembly.
Написание первого WASM-модуля на Rust
Создадим простую функцию, которая будет доступна из JavaScript. Отредактируйте src/lib.rs:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Привет, {}! Добро пожаловать в мир WebAssembly!", name)
}
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
Атрибут #[wasm_bindgen] позволяет экспортировать функции в JavaScript. Обратите внимание: String автоматически конвертируется в JavaScript-строку.
Теперь соберем проект:
wasm-pack build --target web
После сборки в папке pkg появятся файлы:
wasm_rust_demo_bg.wasm— скомпилированный WASM-модульwasm_rust_demo.js— JavaScript-обертка для удобного импортаwasm_rust_demo.d.ts— TypeScript-декларации
Интеграция с JavaScript и HTML
Создайте файл index.html в корне проекта:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rust + WebAssembly Demo</title>
</head>
<body>
<h1>Rust + WebAssembly Пример</h1>
<div>
<h3>Функция greet</h3>
<input type="text" id="nameInput" placeholder="Введите имя" value="Мир">
<button id="greetBtn">Поприветствовать</button>
<p id="greetOutput"></p>
</div>
<div>
<h3>Числа Фибоначчи</h3>
<input type="number" id="fibInput" value="10" min="0" max="40">
<button id="fibBtn">Вычислить</button>
<p id="fibOutput"></p>
</div>
<script type="module">
import init, { greet, fibonacci, add } from './pkg/wasm_rust_demo.js';
async function run() {
await init();
document.getElementById('greetBtn').addEventListener('click', () => {
const name = document.getElementById('nameInput').value;
const result = greet(name);
document.getElementById('greetOutput').textContent = result;
});
document.getElementById('fibBtn').addEventListener('click', () => {
const n = parseInt(document.getElementById('fibInput').value);
const result = fibonacci(n);
document.getElementById('fibOutput').textContent =
`Fibonacci(${n}) = ${result}`;
});
// Демонстрация add
console.log('2 + 3 =', add(2, 3));
}
run();
</script>
</body>
</html>
Запустите HTTP-сервер:
python3 -m http.server 8000
Откройте браузер по адресу http://localhost:8000. Вы увидите работающее приложение, где функции Rust вызываются из JavaScript.
Работа с памятью и сложными типами данных
Иногда нужно передавать более сложные структуры данных. wasm-bindgen поддерживает сериализацию через serde и работу с массивами.
Добавим в Cargo.toml:
[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_wasm_bindgen = "0.6"
Теперь создадим структуру и функцию для работы с ней:
use serde::{Serialize, Deserialize};
use wasm_bindgen::prelude::*;
#[derive(Serialize, Deserialize)]
pub struct Point {
pub x: f64,
pub y: f64,
}
#[wasm_bindgen]
pub fn calculate_distance(p1: JsValue, p2: JsValue) -> JsValue {
let point1: Point = serde_wasm_bindgen::from_value(p1).unwrap();
let point2: Point = serde_wasm