Дайджесты новостей
Иллюстрация прямого межъязыкового моста Bun FFI и Rust без промежуточного слоя Node-API для десктопных приложений.

bffi-rs: нативные биндинги Rust для Bun без тяжеловесного слоя Node-API

Разработка высокопроизводительных нативных расширений для серверного JavaScript годами опиралась на стандарт Node-API и популярную библиотеку napi-rs. Казалось, найдена идеальная формула: пишем критичный к скорости код на Rust, компилируем в разделяемую библиотеку и без проблем вызываем функции из JS. Однако с появлением современных быстрых рантаймов вроде Bun эта идиллия дала трещину. Созданный под архитектуру Node.js и движок V8, слой napi-rs вынужден работать в Bun через громоздкий эмулятор совместимости, теряя преимущества рантайма JavaScriptCore.

Экспериментальный фреймворк bffi-rs предлагает бескомпромиссную альтернативу, делая ставку на нативный модуль bun:ffi и прямое взаимодействие по C-ABI.

Прямой JIT-вызов и нулевое копирование памяти

Главное бутылочное горлышко классического Node-API кроется в постоянном маршалинге аргументов: каждый вызов функции требует преобразования типов через внутренние хэндлы движка, создавая ощутимые задержки в циклах высокой частоты.

В отличие от него, встроенный в Bun механизм FFI компилирует вызовы внешних функций непосредственно в машинные инструкции процессора с помощью собственного JIT-компилятора. Согласно замерам, такой подход ускоряет межъязыковые вызовы в 2–6 раз по сравнению с Node-API. Кроме того, модуль поддерживает передачу бинарных буферов с нулевым копированием (zero-copy): память, выделенная в типизированных массивах TypedArray в JavaScript, передается по прямому указателю в код на Rust без создания промежуточных дубликатов.

Библиотека bffi-rs избавляет разработчика от опасной ручной возни с сырыми указателями (unsafe), предоставляя декларативные процедурные макросы #[bffi]:

// Экспорт высокопроизводительной функции обработки данных на Rust
use bffi_rs::prelude::*;

#[bffi]
pub fn compute_checksum(buffer: &[u8], seed: u32) -> u32 {
    let mut hash = seed;
    for &byte in buffer {
        hash = hash.wrapping_mul(31).wrapping_add(byte as u32);
    }
    hash
}

Макрос автоматически генерирует разделяемую C-совместимую библиотеку и типизированный TypeScript-файл с декларациями.

Подключение к рантайму Bun и десктопный стек

На стороне JavaScript-кода вызов выглядит столь же нативно и лаконично, обращаясь напрямую к скомпилированному модулю через bun:ffi:

import { dlopen, FFIType, ptr } from 'bun:ffi';

const lib = dlopen('./target/release/libmy_module.so', {
  compute_checksum: {
    args: [FFIType.ptr, FFIType.u32, FFIType.u32],
    returns: FFIType.u32,
  },
});

const payload = new Uint8Array([10, 20, 30, 40, 50]);
// Передача сырого указателя буфера без накладных расходов на копирование
const result = lib.symbols.compute_checksum(ptr(payload), payload.length, 1337);
console.log('Вычисленный хеш:', result);

Ключевым драйвером развития bffi-rs стал фреймворк Electrobun — перспективная альтернатива Electron, собирающая сверхлегкие настольные приложения на базе нативных системных WebView (WebKit на macOS, WebView2 на Windows, WebKitGTK на Linux). Связка Bun и Rust через прямой FFI дает десктопным программам мгновенный старт и минимальное потребление оперативной памяти.

Хотя проект пока носит статус экспериментального, он задает новый стандарт производительности для расширений на Rust в современной экосистеме JavaScript.