Добро пожаловать в углублённое изучение того, как Rust интегрируется с экосистемой WebAssembly (Wasm) через инструмент wasm-bindgen. Этот раздел посвящён созданию высокопроизводительных веб-приложений, где Rust выступает в роли языка для генерации WebAssembly, а wasm-bindgen обеспечивает бесшовное взаимодействие между Rust и JavaScript. Мы разберём всё: от основ до тонкостей, с примерами, скрытыми возможностями и практическими советами. Если вы новичок, не переживайте — начнём с азов. Если вы эксперт, найдёте здесь нюансы, которые, возможно, упустили.
WebAssembly — это бинарный формат, который позволяет запускать код на уровне, близком к машинному, прямо в браузере. Он был разработан как дополнение к JavaScript, чтобы повысить производительность для задач, где JS может быть слишком медленным: игры, обработка графики, сложные вычисления. Rust идеально подходит для WebAssembly благодаря своей скорости, безопасности памяти и отсутствию сборщика мусора, что критично для компактного и быстрого кода в Wasm.
Однако WebAssembly сам по себе не "знает" о DOM, JavaScript или других веб-API. Здесь на сцену выходит wasm-bindgen — инструмент, который генерирует связующий код (bindings) между Rust и JavaScript, позволяя вызывать функции в обе стороны и передавать сложные данные.
Прежде чем начать, убедитесь, что у вас есть всё необходимое:
rustup).wasm-pack — утилита для сборки Wasm-проектов. Установите её командой:
            curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | shrustup target add wasm32-unknown-unknown.Примечание: Цель wasm32-unknown-unknown — это стандартная цель для WebAssembly, не зависящая от конкретной платформы. В отличие от wasm32-wasi, она не предполагает наличия системных вызовов, что идеально для браузера.
Давайте создадим простой проект, который использует wasm-bindgen. Начнём с базового примера — функции, которая выводит сообщение в консоль браузера.
cargo new wasm-example --libcd wasm-example.
        Cargo.toml:
            [package]
name = "wasm-example"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
crate-type = ["cdylib"] указывает, что мы создаём динамическую библиотеку для WebAssembly.
        src/lib.rs:
            use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
    log(&format!("Привет, {}!", name));
}Разберём этот код:
use wasm_bindgen::prelude::* импортирует необходимые макросы и типы.#[wasm_bindgen] над extern "C" объявляет внешнюю функцию console.log из JavaScript.#[wasm_bindgen] над greet делает функцию доступной для вызова из JS.wasm-pack:
            wasm-pack build --target web--target web указывает, что мы собираем для браузера.
        index.html) в корне проекта:
            <!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Wasm Example</title>
</head>
<body>
    <script type="module">
        import init, { greet } from './pkg/wasm_example.js';
        async function run() {
            await init();
            greet("Мир");
        }
        run();
    </script>
</body>
</html>
npx serve) и откройте index.html в браузере. В консоли вы увидите "Привет, Мир!".wasm-pack компилирует Rust-код в .wasm-файл и генерирует JS-обёртку в папке pkg/. wasm-bindgen создаёт "мост" между Rust и JS, преобразуя типы данных (например, &str в JS-строку) и обеспечивая вызовы функций. В примере выше greet принимает строку из JS, форматирует её и вызывает console.log.
Теперь усложним задачу: передадим структуру из Rust в JS и обратно.
Обновите src/lib.rs:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct Person {
    name: String,
    age: u32,
}
#[wasm_bindgen]
impl Person {
    #[wasm_bindgen(constructor)]
    pub fn new(name: String, age: u32) -> Person {
        Person { name, age }
    }
    pub fn introduce(&self) {
        log(&format!("Меня зовут {}, мне {} лет.", self.name, self.age));
    }
    pub fn grow_older(&mut self) {
        self.age += 1;
    }
}
#[wasm_bindgen]
pub fn create_and_greet(name: String, age: u32) -> Person {
    let person = Person::new(name, age);
    person.introduce();
    person
}
Обновите index.html:
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Wasm Complex Example</title>
</head>
<body>
    <script type="module">
        import init, { create_and_greet } from './pkg/wasm_example.js';
        async function run() {
            await init();
            const person = create_and_greet("Алексей", 30);
            console.log(person);
            person.grow_older();
            console.log("После grow_older:", person.age);
        }
        run();
    </script>
</body>
</html>
Что здесь происходит?
#[derive(Debug)] позволяет отображать структуру в JS через console.log.#[wasm_bindgen(constructor)] делает new конструктором, доступным в JS как new Person().introduce и grow_older) автоматически становятся частью JS-объекта.После сборки и запуска вы увидите в консоли: "Меня зовут Алексей, мне 30 лет.", а затем возраст увеличится до 31.
wasm-bindgen поддерживает базовые типы (числа, строки), но сложные структуры требуют аннотаций. Например, Vec<T> нужно явно преобразовывать в JS-массив с помощью JsValue.#[wasm_bindgen], функция не будет доступна в JS. Всегда проверяйте экспорт.wasm-bindgen и wasm-pack (см. документацию).wasm-pack для автоматизации сборки и тестирования.///, чтобы облегчить интеграцию с JS-разработчиками.wasm-bindgen поддерживает работу с промисами, DOM-объектами и даже TypeScript (через --typescript в wasm-pack). Например, для асинхронных операций можно использовать JsFuture. Это выходит за рамки базового примера, но мы вернёмся к этому в разделе 5.
На этом мы завершаем разбор wasm-bindgen. Вы узнали, как настроить проект, создать простые и сложные взаимодействия с JS, а также избежать типичных ошибок. В следующем разделе мы перейдём к no_std и встраиваемым системам.
Добро пожаловать в мир встраиваемых систем с Rust! В этом разделе мы погрузимся в использование Rust без стандартной библиотеки (no_std), что делает его идеальным выбором для программирования микроконтроллеров, IoT-устройств и других систем с ограниченными ресурсами. Мы разберём всё: от основ до продвинутых техник, с примерами, скрытыми возможностями и практическими советами. Даже если вы новичок в embedded-разработке, этот раздел проведёт вас через все этапы. Эксперты найдут здесь тонкости и лучшие практики.
По умолчанию Rust использует стандартную библиотеку (std), которая предоставляет удобные абстракции: коллекции, ввод-вывод, многопоточность и т.д. Однако std требует наличия операционной системы и сборщика мусора, что делает её непригодной для встраиваемых систем, где ресурсов мало, а среда выполнения — "голое железо" (bare-metal). Здесь на помощь приходит атрибут #![no_std], который отключает стандартную библиотеку, оставляя только core — минимальный набор инструментов, работающий без ОС.
no_std позволяет писать низкоуровневый код, сохраняя преимущества Rust: безопасность памяти, выразительность и отсутствие undefined behavior. Это делает Rust конкурентом C и C++ в embedded-разработке.
Для работы с no_std вам понадобится:
rustup).thumbv7m-none-eabi для микроконтроллеров Cortex-M. Установите её:
            rustup target add thumbv7m-none-eabiarm-none-eabi-gcc) и утилиты вроде openocd для прошивки.Примечание: Цель thumbv7m-none-eabi подходит для большинства ARM Cortex-M микроконтроллеров. Для других архитектур (RISC-V, AVR) выбирайте соответствующую цель, например, riscv32i-unknown-none-elf.
Давайте напишем простой проект для микроконтроллера, который мигает светодиодом. Мы начнём с минимального примера без внешних зависимостей.
cargo new blinky --bincd blinky.
        Cargo.toml:
            [package]
name = "blinky"
version = "0.1.0"
edition = "2021"
[profile.release]
opt-level = "s"  # Оптимизация для размера
Опция opt-level = "s" минимизирует размер бинарника, что важно для встраиваемых систем.
.cargo/config.toml для указания цели:
            [build]
target = "thumbv7m-none-eabi"src/main.rs:
            #![no_std]
#![no_main]
use core::panic::PanicInfo;
// Точка входа для bare-metal
#[no_mangle]
pub extern "C" fn _start() -> ! {
    loop {
        // Здесь будет логика мигания светодиодом
    }
}
// Обработчик паники (обязателен в no_std)
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}Разберём код:
#![no_std] отключает стандартную библиотеку (в начале файла, так как это внутренняя директива).#![no_main] убирает стандартную точку входа main, заменяя её на _start.#[no_mangle] сохраняет имя функции _start для компоновщика.! как тип возврата указывает, что функция никогда не завершается.#[panic_handler] определяет поведение при панике.cargo build --release --target thumbv7m-none-eabitarget/thumbv7m-none-eabi/release/blinky.
        Этот код пока ничего не делает, кроме бесконечного цикла. Чтобы он мигал светодиодом, нужно взаимодействовать с регистрами микроконтроллера — об этом ниже.
В реальных встраиваемых системах вы напрямую обращаетесь к аппаратным регистрам. Для этого можно использовать "сырой" доступ через указатели или библиотеки абстракции (PAC/HAL). Начнём с простого примера — мигание светодиода на STM32F1.
Обновите Cargo.toml, добавив зависимость от cortex-m:
        
[dependencies]
cortex-m = "0.7"Обновите src/main.rs:
        
#![no_std]
#![no_main]
use cortex_m::asm;
use cortex_m::peripheral::Peripherals;
// Адреса регистров для STM32F1 (пример для порта GPIOA)
const RCC_APB2ENR: *mut u32 = 0x4002_1018 as *mut u32; // Включение тактирования GPIOA
const GPIOA_CRL: *mut u32 = 0x4001_0800 as *mut u32;   // Конфигурация порта
const GPIOA_BSRR: *mut u32 = 0x4001_0810 as *mut u32;  // Установка/сброс пина
#[no_mangle]
pub extern "C" fn _start() -> ! {
    unsafe {
        // Включаем тактирование GPIOA
        RCC_APB2ENR.write_volatile(1 << 2);
        // Настраиваем PA5 как выход (светодиод на STM32F1 Blue Pill)
        GPIOA_CRL.write_volatile(0x4444_4441); // 01: выход, 10 МГц
    }
    loop {
        unsafe {
            // Включаем светодиод (PA5)
            GPIOA_BSRR.write_volatile(1 << 5);
            delay(500_000); // Примитивная задержка
            // Выключаем светодиод
            GPIOA_BSRR.write_volatile(1 << (5 + 16));
            delay(500_000);
        }
    }
}
// Простая задержка
fn delay(count: u32) {
    for _ in 0..count {
        asm::nop();
    }
}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {}
}
Что здесь происходит?
unsafe используется для работы с сырыми указателями.write_volatile обеспечивает запись без оптимизации компилятором.delay — примитивная задержка через nop (неточные тайминги, но для примера достаточно).Соберите и прошейте код на STM32F1 (например, через openocd и arm-none-eabi-objcopy для конверсии в .bin). Светодиод на PA5 начнёт мигать.
"Сырой" доступ к регистрам сложен и опасен. В реальных проектах используют библиотеки абстракции: Peripheral Access Crate (PAC) или Hardware Abstraction Layer (HAL). Например, для STM32 есть stm32f1xx-hal.
Обновите Cargo.toml:
        
[dependencies]
cortex-m = "0.7"
stm32f1xx-hal = { version = "0.10", features = ["stm32f103", "rt"] }
panic-halt = "0.2" # Простой обработчик паникиОбновите src/main.rs:
    
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use stm32f1xx_hal::{pac, prelude::*, timer::Timer};
use panic_halt as _;
#[entry]
fn main() -> ! {
    let dp = pac::Peripherals::take().unwrap();
    let cp = cortex_m::Peripherals::take().unwrap();
    let mut rcc = dp.RCC.constrain();
    let mut gpioa = dp.GPIOA.split(&mut rcc.apb2);
    let mut led = gpioa.pa5.into_push_pull_output(&mut gpioa.crl);
    let mut timer = Timer::syst(cp.SYST, &mut rcc.apb1).start_count_down(1.hz());
    loop {
        led.set_high().unwrap();
        timer.wait().unwrap();
        led.set_low().unwrap();
        timer.wait().unwrap();
    }
}Что изменилось?
#[entry] из cortex-m-rt заменяет _start.#[panic_handler] компиляция завершится ошибкой. Используйте panic-halt или кастомный обработчик.no_std минимизирует бинарник, но зависимости вроде HAL могут его увеличить. Используйте opt-level = "s" или "z".std нет println!. Используйте UART или RTT (Real-Time Transfer) для логов.memory.x) соответствует вашему устройству.defmt или log с RTT для отладки.cortex-m::asm::wfi).no_std поддерживает асинхронное программирование через embassy — фреймворк для встраиваемых систем. Также есть alloc для динамической памяти в no_std с кастомным аллокатором (например, wee_alloc).
На этом мы завершаем разбор no_std. Вы узнали, как писать bare-metal код, работать с регистрами и использовать HAL. В следующем разделе мы перейдём к сетевым приложениям.
Rust благодаря своей производительности, безопасности и богатой экосистеме библиотек (crates) стал популярным выбором для разработки сетевых приложений. В этом разделе мы углубимся в мир сетевого программирования на Rust, рассмотрим ключевые библиотеки, их возможности, типичные сценарии использования, а также разберем примеры кода. Мы не ограничимся поверхностным обзором — вы получите полное представление о том, как писать надежные, масштабируемые и безопасные сетевые приложения, избегая распространенных ошибок.
Прежде чем погрузиться в библиотеки, давайте разберем, почему Rust так хорош в этой области:
async/await позволяет эффективно обрабатывать тысячи соединений без лишних затрат.Теперь перейдем к обзору ключевых библиотек и их применению.
Tokio — это асинхронный runtime, который стал стандартом де-факто для сетевых приложений в Rust. Он предоставляет инструменты для работы с TCP/UDP-сокетами, таймерами, потоками и многим другим.
TcpStream, UdpSocket).tokio::runtime.async/await.use tokio::net::TcpListener; use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> tokio::io::Result<()> { let listener = TcpListener::bind("127.0.0.1:8080").await?; println!("Сервер запущен на 
    127.0.0.1:8080"); loop {
        let (mut socket, addr) = listener.accept().await?; println!("Новое соединение: {}", addr); tokio::spawn(async move { let mut buffer = [0; 1024]; 
            match socket.read(&mut buffer).await {
                Ok(n) if n > 0 => { println!("Получено: {}", String::from_utf8_lossy(&buffer[..n])); 
                    socket.write_all(&buffer[..n]).await.unwrap(); // Эхо
                }
                _ => println!("Соединение закрыто: {}", addr),
            }
        });
    }
}Объяснение:
TcpListener::bind создает сервер на указанном адресе.accept асинхронно ожидает входящие соединения.tokio::spawn запускает обработку каждого клиента в отдельной задаче.Совет: Используйте cargo add tokio --features full для полной функциональности.
Hyper — это низкоуровневая библиотека для работы с HTTP. Она часто используется как основа для более высокоуровневых фреймворков (например, Axum).
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn}; use std::convert::Infallible; async fn handle(_req: Request<Body>) -> 
Result<Response<Body>, Infallible> {
    Ok(Response::new(Body::from("Hello, Hyper!")))
}
#[tokio::main]
async fn main() { let addr = ([127, 0, 0, 1], 3000).into(); let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle))
    });
    let server = Server::bind(&addr).serve(make_svc); println!("Сервер запущен на http://{}", addr); if let Err(e) = server.await { eprintln!("Ошибка 
        сервера: {}", e);
    }
}Объяснение:
service_fn оборачивает обработчик запросов.make_service_fn создает 
сервис для каждого соединения.Совет: Для реальных приложений используйте Axum поверх Hyper.
Reqwest — это высокоуровневый HTTP-клиент, построенный на Hyper, но с удобным API.
use reqwest::Client;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Post { id: u32, title: String,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> { let client = Client::new(); let post: Post = client 
        .get("https://jsonplaceholder.typicode.com/posts/1") .send() .await? .json() .await?;
    println!("Получен пост: {:?}", post); Ok(())
}Объяснение:
Client — переиспользуемый клиент для экономии ресурсов.json() 
десериализует ответ в структуру Post.Совет: Добавьте cargo add serde serde_json для работы с JSON.
Actix-web — это мощный веб-фреймворк для создания серверов REST API и веб-приложений.
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
async fn greet() -> impl Responder { HttpResponse::Ok().body("Hello, Actix!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .route("/", web::get().to(greet))
    })
    .bind("127.0.0.1:8080")? .run() .await
}Объяснение:
route задает маршрут для GET-запросов.HttpServer запускает сервер 
на указанном адресе.Совет: Используйте cargo add actix-web и изучите документацию для middleware.
Result и логируйте ошибки с log или 
tracing.tokio::test для асинхронных тестов.Сетевые приложения в Rust — это сочетание мощи и безопасности. Выбор библиотеки зависит от ваших задач: от простых клиентов (Reqwest) до сложных серверов (Actix-web, Hyper, Tokio). В следующих разделах мы рассмотрим другие аспекты экосистемы Rust, но уже сейчас вы можете начать экспериментировать с примерами выше.
Rust, благодаря своей производительности, безопасности и гибкости, стал популярным выбором для разработки кроссплатформенных приложений. В этом разделе мы подробно разберём, как использовать кроссплатформенные фреймворки в экосистеме Rust, какие инструменты доступны, их сильные и слабые стороны, а также практические примеры их применения. Мы рассмотрим как графические интерфейсы (GUI), так и кроссплатформенные решения для мобильных и десктопных приложений. Этот раздел поможет вам понять, как Rust может быть использован для создания приложений, работающих на Windows, macOS, Linux, Android, iOS и даже в браузере через WebAssembly.
Кроссплатформенные фреймворки позволяют разработчикам писать код один раз и запускать его на разных платформах с минимальными изменениями. В мире Rust это особенно важно, так как язык изначально ориентирован на низкоуровневую производительность, что делает его конкурентом C++ в таких областях, как разработка GUI или мобильных приложений. Однако, в отличие от языков вроде JavaScript или Python, где кроссплатформенность часто достигается за счёт интерпретаторов или виртуальных машин, Rust полагается на компиляцию в нативный код, что требует от фреймворков особой гибкости.
Экосистема Rust предлагает несколько фреймворков для кроссплатформенной разработки. Мы рассмотрим основные из них, их особенности и сценарии использования.
Tauri — это легковесный фреймворк для создания кроссплатформенных десктопных приложений с использованием веб-технологий (HTML, CSS, JavaScript) для интерфейса и Rust для бэкенда. Tauri позиционируется как альтернатива Electron, но с меньшим потреблением ресурсов и большей безопасностью.
Пример простого проекта с Tauri:
 <!-- Файл src-tauri/src/main.rs --> use tauri::command;
#[command]
fn greet(name: &str) -> String { format!("Привет, {}! Это Rust в Tauri.", name)
}
fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![greet]) .run(tauri::generate_context!()) .expect("Ошибка запуска Tauri");
}
 В этом примере мы создаём команду greet, которую можно вызвать из JavaScript фронтенда. Для запуска нужно настроить 
src-tauri/tauri.conf.json и добавить HTML-файл в src-tauri/index.html.
Нюанс: Tauri требует установленного веб-движка на целевой системе (например, WebView2 на Windows). Убедитесь, что ваша целевая аудитория имеет совместимую ОС.
Dioxus — это фреймворк для создания кроссплатформенных приложений с единым кодом для десктопа, веба и мобильных устройств. 
Он вдохновлён React и использует синтаксис, похожий на JSX (через макрос rsx!).
Пример приложения с Dioxus:
 use dioxus::prelude::*; fn main() { dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); cx.render(rsx! { h1 { "Счётчик: {count}" } button { onclick: move |_| count += 1, 
            "Увеличить"
        }
    })
}
 Этот код создаёт простое десктопное приложение с кнопкой и счётчиком. Для сборки под WebAssembly нужно изменить таргет на 
wasm32-unknown-unknown и использовать dioxus build --release.
Подводный камень: Dioxus пока экспериментирует с мобильной поддержкой, поэтому для продакшена лучше протестировать стабильность на iOS/Android.
Druid — это нативный кроссплатформенный GUI-фреймворк, написанный на Rust. Он ориентирован на производительность и минимализм, предлагая инструменты для создания десктопных приложений.
Пример с Druid:
 use druid::widget::{Button, Flex, Label}; use druid::{AppLauncher, LocalizedString, Widget, WindowDesc, Data};
#[derive(Clone, Data)]
struct AppState { count: i32,
}
fn build_ui() -> impl Widget<AppState> { let label = Label::new(|data: &AppState, _env: &_| format!("Счётчик: {}", data.count)); let button = 
    Button::new("Увеличить")
        .on_click(|_ctx, data: &mut AppState, _env| data.count += 1); Flex::column() .with_child(label) .with_child(button)
}
fn main() { let window = WindowDesc::new(build_ui()) .title(LocalizedString::new("Druid Пример")); AppLauncher::with_window(window) .launch(AppState { 
        count: 0 }) .expect("Ошибка запуска");
}
 Этот код создаёт окно с кнопкой и счётчиком. Druid использует декларативный подход к построению интерфейса, что упрощает поддержку.
Совет: Используйте druid::lens для управления сложными состояниями, чтобы избежать избыточного кода.
| Фреймворк | Платформы | Производительность | Размер бинарника | Сложность | 
|---|---|---|---|---|
| Tauri | Десктоп, мобильные (экспериментально) | Высокая | Малый | Средняя | 
| Dioxus | Десктоп, веб, мобильные | Средняя | Средний | Низкая | 
| Druid | Десктоп | Очень высокая | Средний | Высокая | 
cross или Docker для сборки под разные платформы. Например, cross build --target aarch64-apple-darwin для macOS 
  ARM.Кроссплатформенные фреймворки в Rust открывают широкие возможности для создания приложений, работающих на разных устройствах. Tauri, Dioxus и Druid — это лишь вершина айсберга, и экосистема продолжает расти. Выбор подходящего инструмента зависит от ваших целей, но все они демонстрируют силу Rust: безопасность, производительность и гибкость.
Когда мы говорим о сборке Rust-проектов для браузера, мы имеем в виду использование WebAssembly (WASM) как целевой платформы. WebAssembly — это низкоуровневый байт-код, который выполняется в современных браузерах с почти нативной скоростью. Rust идеально подходит для этой задачи благодаря своей производительности, безопасности и гибкости. В этом разделе мы разберем несколько примеров сборки Rust-кода для браузера, начиная с простого "Hello, World!" и заканчивая более сложными сценариями, такими как взаимодействие с DOM и обработка событий. Мы также рассмотрим инструменты, настройки и потенциальные "подводные камни".
Rust позволяет писать высокопроизводительный код, который компилируется в WASM, избегая накладных расходов JavaScript там, где это возможно. При 
    этом инструменты вроде wasm-bindgen упрощают интеграцию с JavaScript и DOM, делая Rust мощным выбором для веб-разработки. Сценарии 
    использования включают:
Для сборки Rust-проектов под браузер вам понадобятся:
wasm32-unknown-unknown.Установите их, если еще не сделали:
rustup target add wasm32-unknown-unknown cargo install wasm-pack npm install -g npm # Если 
npm еще не установленДавайте начнем с минимального примера, который выводит сообщение в консоль браузера.
Создайте новый проект Rust:
cargo new wasm-hello --lib
cd wasm-helloCargo.tomlДобавьте зависимости и укажите, что это библиотека для WebAssembly:
[package]
name = "wasm-hello" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2"crate-type = 
    ["cdylib"] указывает, что мы создаем динамическую библиотеку, совместимую с WASM. wasm-bindgen позволяет взаимодействовать с 
    JavaScript.
src/lib.rsЗамените содержимое файла:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" { fn alert(s: &str); // Объявляем внешнюю функцию JavaScript
}
#[wasm_bindgen]
pub fn greet() { alert("Hello, World from Rust!");
}#[wasm_bindgen] — макрос, который делает функции доступными для JavaScript. extern "C" — блок для объявления внешних 
    функций (в данном случае alert из JavaScript).
wasm-packСоберите проект:
wasm-pack build --target webФлаг --target web указывает, что мы создаем модуль для прямого использования в 
    браузере.
Создайте файл index.html в корне проекта:
<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <title>Rust WASM Hello</title> </head> <body> <script type="module"> 
        import init, { greet } from './pkg/wasm_hello.js'; async function run() {
            await init(); // Инициализация WASM greet(); // Вызов функции
        }
        run(); </script> </body> </html>Запустите локальный сервер (например, с помощью 
    python):
python3 -m http.server 8000Откройте http://localhost:8000 в браузере и проверьте 
    всплывающее окно с "Hello, World from Rust!".
Нюанс: Если вы видите ошибку "module not found", убедитесь, что путь к 
    pkg/wasm_hello.js в index.html правильный. После сборки wasm-pack создает папку pkg с 
    сгенерированными файлами.
Теперь усложним задачу: изменим текст элемента на странице.
src/lib.rsuse wasm_bindgen::prelude::*;
use web_sys::window; // Для работы с DOM
#[wasm_bindgen]
pub fn update_dom() -> Result<(), JsValue> { let window = window().ok_or("No global window exists")?; let document = window.document().ok_or("No 
    document exists")?; let element = document
        .get_element_by_id("my-text") .ok_or("Element not found")?; element.set_text_content(Some("Updated by Rust!")); Ok(())
}web_sys — это часть wasm-bindgen, предоставляющая привязки к веб-API. Мы используем Result, чтобы 
    обрабатывать ошибки (например, если элемент не найден).
Cargo.tomlДобавьте web-sys с нужными 
    функциями:
[dependencies.web-sys]
version = "0.3" features = ["Window", "Document", "Element"]index.html<!DOCTYPE html> 
<html> <head>
    <meta charset="UTF-8"> <title>Rust WASM DOM</title> </head> <body> <p id="my-text">Original text</p> 
    <script type="module">
        import init, { update_dom } from './pkg/wasm_hello.js'; async function run() { await init(); update_dom();
        }
        run(); </script> </body> </html>wasm-pack build --target web python3 -m 
http.server 8000Текст "Original text" изменится на "Updated by Rust!".
Подводный камень: Если вы забудете включить нужные функции в 
    features для web-sys, компилятор выдаст ошибку. Всегда проверяйте документацию web-sys на crates.io.
Добавим кнопку, которая вызывает Rust-функцию при клике.
src/lib.rsuse wasm_bindgen::prelude::*;
use web_sys::{window, Event, HtmlButtonElement}; use wasm_bindgen::JsCast;
#[wasm_bindgen]
pub fn setup_button() -> Result<(), JsValue> { let window = window().ok_or("No window")?; let document = window.document().ok_or("No document")?; 
    let button = document
        .get_element_by_id("my-button") .ok_or("Button not found")? .dyn_into::<HtmlButtonElement>()?; let closure = Closure::wrap(Box::new(|_event: 
    Event| {
        button.set_text_content(Some("Clicked via Rust!"));
    }) as Box<dyn FnMut(Event)>);
    button.add_event_listener_with_callback("click", closure.as_ref().unchecked_ref())?; closure.forget(); // Предотвращаем освобождение памяти Ok(())
}Closure нужен для создания JavaScript-совместимых замыканий. forget() предотвращает уничтожение замыкания после выхода из 
    функции.
Cargo.toml[dependencies.web-sys]
version = "0.3" features = ["Window", "Document", "HtmlButtonElement", "Event"]index.html<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <title>Rust WASM Events</title> </head> <body> <button 
    id="my-button">Click me!</button> <script type="module">
        import init, { setup_button } from './pkg/wasm_hello.js'; async function run() { await init(); setup_button();
        }
        run(); </script> </body> </html>После клика по кнопке текст изменится на "Clicked via Rust!".
Совет: Использование Closure требует осторожности. Если вы забудете вызвать forget(), 
    обработчик события не сработает, так как замыкание будет уничтожено.
wasm-opt из Binaryen для уменьшения размера WASM-файла: wasm-opt -Os 
            pkg/wasm_hello_bg.wasm -o pkg/wasm_hello_bg.wasmconsole_error_panic_hook для вывода паник в консоль браузера: [dependencies] 
console_error_panic_hook = "0.1"#[wasm_bindgen] pub fn init() { console_error_panic_hook::set_once();
}init() асинхронна, не забывайте использовать await.dyn_into может завершиться ошибкой, всегда проверяйте результат.