Rust ve JavaScript Arasında Veri Aktarımı: wasm-bindgen Kullanımı
WebAssembly ile çalışmaya başladığınızda en büyük sorulardan biri şu oluyor: “Rust tarafında hesapladığım şeyi JavaScript’e nasıl aktaracağım?” Sadece sayı gönderip almak değil, karmaşık struct’lar, string’ler, callback’ler… Bunların hepsini yönetmek başlı başına bir iş. İşte tam bu noktada wasm-bindgen devreye giriyor ve hayatınızı inanılmaz ölçüde kolaylaştırıyor.
Bu yazıda wasm-bindgen’in temellerinden başlayıp gerçek dünya senaryolarına kadar her şeyi ele alacağız. Sysadmin perspektifinden baktığınızda bu konu ilk bakışta “frontend meselesi” gibi görünebilir, ama edge computing, serverless fonksiyonlar ve performans kritik araçlar geliştirdiğinizde Rust-JS sınırını iyi anlamak büyük fark yaratıyor.
wasm-bindgen Nedir ve Neden Önemlidir?
wasm-bindgen, Rust ile JavaScript arasında köprü kuran bir araç zinciri. WebAssembly spec’i temel olarak sayısal türleri destekler; i32, i64, f32, f64 ve bellek işaretçileri. Yani teorik olarak JavaScript’ten Wasm fonksiyonuna string göndermenin “spec’e uygun” yolu yok. String’i belleğe yazmak, pointer’ı ve uzunluğu geçirmek, sonra okumak gerekiyor. Bu işi elle yapmak hem yorucu hem hata prone.
wasm-bindgen bu boilerplate’i otomatik üretiyor. Rust kodunuza #[wasm_bindgen] attribute’u koyuyorsunuz, araç zinciri hem Rust binding’lerini hem de JavaScript glue kodunu üretiyor. Sonuç olarak JavaScript tarafında sanki normal bir JS modülü kullanıyormuşsunuz gibi yazıyorsunuz.
Proje Kurulumu
Önce gerekli araçları kuralım. Rust toolchain’iniz zaten varsa işin büyük kısmı hazır.
# wasm-pack kurulumu (wasm-bindgen'in build aracı)
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# Alternatif olarak cargo ile
cargo install wasm-pack
# wasm32 target ekle
rustup target add wasm32-unknown-unknown
# Yeni proje oluştur
cargo new --lib wasm-demo
cd wasm-demo
Cargo.toml dosyasını düzenlememiz gerekiyor:
cat > Cargo.toml << 'EOF'
[package]
name = "wasm-demo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["console", "Window", "Document"] }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6"
[profile.release]
opt-level = "s"
lto = true
EOF
crate-type = ["cdylib"] kritik. Bu olmadan Wasm çıktısı üretemezsiniz. rlib ise Rust test’leri çalıştırabilmek için ekliyoruz.
Temel Veri Türlerini Aktarma
En basit örnekten başlayalım. src/lib.rs dosyamıza ilk fonksiyonları yazıyoruz:
use wasm_bindgen::prelude::*;
// Basit sayısal işlem - doğrudan ABI uyumlu
#[wasm_bindgen]
pub fn toplama(a: i32, b: i32) -> i32 {
a + b
}
// String gönderip string alma
#[wasm_bindgen]
pub fn selamlama(isim: &str) -> String {
format!("Merhaba, {}! Rust tarafından selamlar.", isim)
}
// Boolean döndüren fonksiyon
#[wasm_bindgen]
pub fn asal_mi(n: u32) -> bool {
if n < 2 {
return false;
}
for i in 2..=(n as f64).sqrt() as u32 {
if n % i == 0 {
return false;
}
}
true
}
// JavaScript tarafında console.log kullanmak için
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
// Macro ile daha temiz kullanım
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
#[wasm_bindgen]
pub fn debug_mesaj_gonder(mesaj: &str) {
console_log!("Rust'tan JS console'a: {}", mesaj);
}
Bunu build edip test edelim:
# Development build
wasm-pack build --target web --out-dir pkg
# Üretilen dosyaları görelim
ls -la pkg/
# wasm_demo_bg.wasm
# wasm_demo.js
# wasm_demo.d.ts (TypeScript tanımları)
# package.json
JavaScript tarafında kullanımı şu şekilde:
import init, { toplama, selamlama, asal_mi } from './pkg/wasm_demo.js';
async function main() {
// Wasm modülünü başlat
await init();
console.log(toplama(5, 3)); // 8
console.log(selamlama("Ahmet")); // "Merhaba, Ahmet! Rust tarafından selamlar."
console.log(asal_mi(17)); // true
console.log(asal_mi(18)); // false
}
main();
Struct’ları JavaScript’e Açma
Gerçek uygulamalarda sayılar ve string’lerle geçinemezsiniz. Karmaşık nesneleri aktarmanız gerekir. wasm-bindgen bunu iki farklı şekilde destekliyor.
wasm_bindgen ile Struct Export
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct SunucuMetrigi {
cpu_kullanim: f64,
bellek_kullanim: f64,
disk_kullanim: f64,
aktif_baglanti: u32,
}
#[wasm_bindgen]
impl SunucuMetrigi {
// Constructor - new() adı önemli, JS tarafında new SunucuMetrigi() olarak kullanılır
#[wasm_bindgen(constructor)]
pub fn new(cpu: f64, bellek: f64, disk: f64, baglanti: u32) -> SunucuMetrigi {
SunucuMetrigi {
cpu_kullanim: cpu,
bellek_kullanim: bellek,
disk_kullanim: disk,
aktif_baglanti: baglanti,
}
}
// Getter metodları
#[wasm_bindgen(getter)]
pub fn cpu_kullanim(&self) -> f64 {
self.cpu_kullanim
}
#[wasm_bindgen(getter)]
pub fn bellek_kullanim(&self) -> f64 {
self.bellek_kullanim
}
// Hesaplama metodu
pub fn kritik_durum_mu(&self) -> bool {
self.cpu_kullanim > 90.0 || self.bellek_kullanim > 95.0
}
pub fn ozet_rapor(&self) -> String {
format!(
"CPU: {:.1}% | Bellek: {:.1}% | Disk: {:.1}% | Bağlantı: {}",
self.cpu_kullanim, self.bellek_kullanim,
self.disk_kullanim, self.aktif_baglanti
)
}
}
JavaScript tarafında şu şekilde kullanılıyor:
import init, { SunucuMetrigi } from './pkg/wasm_demo.js';
async function metrikKontrol() {
await init();
const metrik = new SunucuMetrigi(87.5, 72.3, 45.0, 234);
console.log(metrik.cpu_kullanim); // 87.5
console.log(metrik.kritik_durum_mu()); // false (90'ın altında)
console.log(metrik.ozet_rapor());
// Önemli: wasm_bindgen struct'ları Drop gerektirir
// Bellek sızıntısını önlemek için
metrik.free();
}
Kritik nokta: #[wasm_bindgen] ile export edilen struct’lar JavaScript tarafında referans sayımlı değil. Manuel olarak .free() çağırmanız gerekiyor, yoksa Wasm heap’inde bellek sızıntısı oluşur. Bunu yönetmenin en iyi yolu try/finally bloğu veya modern JavaScript’te using keyword’ü.
Serde ile Karmaşık Veri Yapıları
Büyük ve iç içe geçmiş veri yapıları için serde-wasm-bindgen kombinasyonu çok daha temiz bir yaklaşım sunuyor:
use wasm_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct LogKaydi {
pub zaman_damgasi: u64,
pub seviye: String,
pub mesaj: String,
pub kaynak_ip: Option<String>,
pub etiketler: Vec<String>,
}
#[derive(Serialize, Deserialize)]
pub struct LogAnaliz {
pub toplam_kayit: usize,
pub hata_sayisi: usize,
pub uyari_sayisi: usize,
pub kritik_ipler: Vec<String>,
pub benzersiz_ipler: usize,
}
#[wasm_bindgen]
pub fn log_analiz_et(log_verisi: JsValue) -> Result<JsValue, JsValue> {
// JavaScript'ten gelen array'i Rust Vec'e dönüştür
let kayitlar: Vec<LogKaydi> = serde_wasm_bindgen::from_value(log_verisi)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let mut hata_sayisi = 0;
let mut uyari_sayisi = 0;
let mut kritik_ipler: Vec<String> = Vec::new();
let mut benzersiz_ipler = std::collections::HashSet::new();
for kayit in &kayitlar {
match kayit.seviye.as_str() {
"ERROR" | "CRITICAL" => {
hata_sayisi += 1;
kritik_ipler.push(kayit.mesaj.clone());
}
"WARN" | "WARNING" => uyari_sayisi += 1,
_ => {}
}
if let Some(ref ip) = kayit.kaynak_ip {
benzersiz_ipler.insert(ip.clone());
}
}
let analiz = LogAnaliz {
toplam_kayit: kayitlar.len(),
hata_sayisi,
uyari_sayisi,
kritik_ipler: kritik_ipler.into_iter().take(10).collect(),
benzersiz_ipler: benzersiz_ipler.len(),
};
serde_wasm_bindgen::to_value(&analiz)
.map_err(|e| JsValue::from_str(&e.to_string()))
}
Bu yaklaşımın güzelliği: JavaScript’te normal objelerle çalışıyormuşsunuz gibi hissediyorsunuz. Struct’ları elle yönetmenize gerek yok.
JavaScript Callback’lerini Rust’tan Çağırma
Bazen Rust tarafında asenkron işlemler yaparken JavaScript callback’lerini tetiklemeniz gerekiyor. Özellikle ilerleme bildirimi göndermek, log akışı oluşturmak gibi senaryolarda:
use wasm_bindgen::prelude::*;
use js_sys::Function;
#[wasm_bindgen]
pub fn buyuk_dosya_isle(
veri_boyutu: u32,
ilerleme_callback: &Function,
) -> Result<String, JsValue> {
let adim_sayisi = 100u32;
let adim_boyutu = veri_boyutu / adim_sayisi;
for i in 0..adim_sayisi {
// Simüle edilmiş iş yükü
let _toplam: u64 = (0..adim_boyutu as u64).sum();
// Her 10 adımda bir ilerleme bildir
if i % 10 == 0 {
let yuzde = JsValue::from_f64((i as f64 / adim_sayisi as f64) * 100.0);
ilerleme_callback
.call1(&JsValue::NULL, &yuzde)
.map_err(|e| e)?;
}
}
Ok(format!("{} byte işlendi.", veri_boyutu))
}
JavaScript tarafında:
import init, { buyuk_dosya_isle } from './pkg/wasm_demo.js';
async function dosyaIsle() {
await init();
const progressBar = document.getElementById('progress');
const sonuc = buyuk_dosya_isle(1_000_000, (yuzde) => {
progressBar.style.width = `${yuzde}%`;
console.log(`İşlem: %${yuzde.toFixed(0)}`);
});
console.log(sonuc); // "1000000 byte işlendi."
}
Promise ve Async/Await Entegrasyonu
Modern JavaScript tamamen async. wasm-bindgen’de wasm-bindgen-futures crate’i ile Rust Future’larını JS Promise’lara dönüştürebilirsiniz:
# Cargo.toml'a ekle
# wasm-bindgen-futures = "0.4"
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, Response};
#[wasm_bindgen]
pub async fn api_verisi_cek(url: String) -> Result<JsValue, JsValue> {
let opts = RequestInit::new();
opts.set_method("GET");
let request = Request::new_with_str_and_init(&url, &opts)?;
let window = web_sys::window().unwrap();
let response_value = JsFuture::from(window.fetch_with_request(&request)).await?;
let response: Response = response_value.dyn_into()?;
if !response.ok() {
return Err(JsValue::from_str(&format!(
"HTTP hatası: {}", response.status()
)));
}
let json = JsFuture::from(response.json()?).await?;
Ok(json)
}
// Paralel işlem örneği - birden fazla endpoint'i aynı anda sorgula
#[wasm_bindgen]
pub async fn coklu_metrik_topla(endpoint_listesi: Vec<String>) -> JsValue {
// Bu örnekte basitleştirilmiş simülasyon
let mut sonuclar = Vec::new();
for endpoint in &endpoint_listesi {
sonuclar.push(format!("{}: OK", endpoint));
}
serde_wasm_bindgen::to_value(&sonuclar).unwrap_or(JsValue::NULL)
}
JavaScript tarafında async fonksiyonlar doğrudan await ile kullanılabiliyor:
import init, { api_verisi_cek, coklu_metrik_topla } from './pkg/wasm_demo.js';
async function main() {
await init();
try {
const veri = await api_verisi_cek('https://api.ornek.com/metrikler');
console.log(veri);
} catch (hata) {
console.error('API hatası:', hata);
}
const endpointler = [
'https://sunucu1.ornek.com/health',
'https://sunucu2.ornek.com/health',
'https://sunucu3.ornek.com/health'
];
const sonuclar = await coklu_metrik_topla(endpointler);
console.log(sonuclar);
}
Gerçek Dünya Senaryosu: Log Parser
Sysadmin olarak düşündüğünüzde bu teknolojinin en güçlü kullanım alanlarından biri şu: tarayıcı tabanlı log analiz araçları. Büyük log dosyalarını sunucuya yüklemek yerine, Wasm ile client tarafında parse edip analiz edebilirsiniz. Hem güvenli hem hızlı.
use wasm_bindgen::prelude::*;
use serde::Serialize;
#[derive(Serialize)]
pub struct NginxLogSatiri {
pub ip: String,
pub metod: String,
pub yol: String,
pub durum_kodu: u16,
pub yanit_boyutu: u64,
pub kullanici_ajan: String,
}
#[derive(Serialize)]
pub struct NginxAnalizSonucu {
pub toplam_istek: usize,
pub basarili_istek: usize,
pub hata_istek: usize,
pub en_cok_ziyaret_edilen: Vec<(String, usize)>,
pub durum_kodu_dagilimi: Vec<(u16, usize)>,
pub ortalama_yanit_boyutu: f64,
}
#[wasm_bindgen]
pub fn nginx_log_parse_et(log_icerigi: &str) -> JsValue {
let mut satirlar: Vec<NginxLogSatiri> = Vec::new();
let mut yol_sayaci: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
let mut durum_sayaci: std::collections::HashMap<u16, usize> = std::collections::HashMap::new();
let mut toplam_boyut: u64 = 0;
for satir in log_icerigi.lines() {
// Basitleştirilmiş parser - gerçekte regex kullanırsınız
let parcalar: Vec<&str> = satir.splitn(10, ' ').collect();
if parcalar.len() < 7 {
continue;
}
let ip = parcalar[0].to_string();
let metod = parcalar[5].trim_start_matches('"').to_string();
let yol = parcalar[6].to_string();
let durum_kodu: u16 = parcalar[8].parse().unwrap_or(0);
let yanit_boyutu: u64 = parcalar[9].parse().unwrap_or(0);
*yol_sayaci.entry(yol.clone()).or_insert(0) += 1;
*durum_sayaci.entry(durum_kodu).or_insert(0) += 1;
toplam_boyut += yanit_boyutu;
satirlar.push(NginxLogSatiri {
ip,
metod,
yol,
durum_kodu,
yanit_boyutu,
kullanici_ajan: String::new(),
});
}
let mut en_cok_ziyaret: Vec<(String, usize)> = yol_sayaci.into_iter().collect();
en_cok_ziyaret.sort_by(|a, b| b.1.cmp(&a.1));
en_cok_ziyaret.truncate(10);
let mut durum_dagilimi: Vec<(u16, usize)> = durum_sayaci.into_iter().collect();
durum_dagilimi.sort_by_key(|x| x.0);
let basarili = satirlar.iter().filter(|s| s.durum_kodu < 400).count();
let toplam = satirlar.len();
let sonuc = NginxAnalizSonucu {
toplam_istek: toplam,
basarili_istek: basarili,
hata_istek: toplam - basarili,
en_cok_ziyaret_edilen: en_cok_ziyaret,
durum_kodu_dagilimi: durum_dagilimi,
ortalama_yanit_boyutu: if toplam > 0 {
toplam_boyut as f64 / toplam as f64
} else {
0.0
},
};
serde_wasm_bindgen::to_value(&sonuc).unwrap_or(JsValue::NULL)
}
Bu parser’ı test etmek için:
# Test verisi oluştur
cat > test_log.txt << 'EOF'
192.168.1.1 - - [01/Jan/2024:10:00:01 +0300] "GET /api/users HTTP/1.1" 200 1234
192.168.1.2 - - [01/Jan/2024:10:00:02 +0300] "POST /api/login HTTP/1.1" 401 89
192.168.1.1 - - [01/Jan/2024:10:00:03 +0300] "GET /api/dashboard HTTP/1.1" 200 5678
EOF
# Build
wasm-pack build --target web --release
# Basit bir web server ile test et
python3 -m http.server 8080
Performans ve Boyut Optimizasyonu
Production’a geçmeden önce Wasm dosya boyutunu ve performansını optimize etmek gerekiyor:
# wasm-opt ile boyut küçültme (binaryen gerekli)
apt-get install binaryen # ya da brew install binaryen
# wasm-pack release build zaten wasm-opt çalıştırır
wasm-pack build --target web --release
# Manuel optimizasyon
wasm-opt -Os -o optimized.wasm pkg/wasm_demo_bg.wasm
# Boyut karşılaştırması
ls -lh pkg/wasm_demo_bg.wasm
ls -lh optimized.wasm
# gzip ile gerçek transfer boyutunu gör
gzip -k pkg/wasm_demo_bg.wasm
ls -lh pkg/wasm_demo_bg.wasm.gz
Cargo.toml‘da profil ayarları da kritik:
# Cargo.toml release profil
# opt-level = "s" -> boyut odaklı optimizasyon
# opt-level = "z" -> maksimum boyut küçültme (bazen "s"den iyi)
# lto = true -> link-time optimization
# codegen-units = 1 -> daha iyi optimizasyon, yavaş derleme
# panic handler değiştirme - boyutu önemli ölçüde düşürür
# panic = "abort"
TypeScript Entegrasyonu
wasm-bindgen otomatik .d.ts dosyaları üretiyor. Bu dosyaları TypeScript projenizde kullanmak oldukça temiz:
# Üretilen .d.ts içeriğini kontrol et
cat pkg/wasm_demo.d.ts
# TypeScript projesine entegre et
cp -r pkg/ my-ts-project/src/wasm/
# tsconfig.json'da path mapping ekle
# "paths": { "wasm-demo": ["./src/wasm/wasm_demo"] }
TypeScript ile kullanım:
import init, { SunucuMetrigi, log_analiz_et } from './wasm/wasm_demo';
interface LogKaydi {
zaman_damgasi: number;
seviye: string;
mesaj: string;
kaynak_ip?: string;
etiketler: string[];
}
async function analizBaslat(kayitlar: LogKaydi[]): Promise<void> {
await init();
const sonuc = log_analiz_et(kayitlar);
console.log(`Toplam: ${sonuc.toplam_kayit}, Hata: ${sonuc.hata_sayisi}`);
const metrik = new SunucuMetrigi(75.0, 60.0, 30.0, 150);
console.log(metrik.ozet_rapor());
metrik.free(); // Belleği temizle
}
Sonuç
wasm-bindgen, Rust ve JavaScript arasındaki sınırı neredeyse görünmez kılıyor. String’ler, struct’lar, callback’ler, Promise’lar… Bunların hepsini oldukça doğal bir şekilde iki dünya arasında taşıyabiliyorsunuz.
Sysadmin bakış açısıyla değerlendirdiğinizde bu teknolojinin en büyük değeri şuradan geliyor: CPU yoğun işleri (log parsing, şifreleme, veri sıkıştırma, metrik analizi) Rust’ın hızıyla yapıp sonuçları JavaScript UI’ına aktarabiliyorsunuz. Client tarafında çalıştığı için sunucu yükü yok, hassas veri sunucuya gönderilmiyor.
Dikkat edilmesi gereken noktalara gelince: struct’ların bellek yönetimini ihmal etmeyin, .free() çağrılarını unutmayın. Production’da mutlaka wasm-opt çalıştırın, boyut farkı ciddi oluyor. Hata yönetiminde Result pattern’ini benimseyip JavaScript tarafında düzgün try/catch kullanın.
Edge computing platformlarında (Cloudflare Workers, Fastly Compute@Edge) da wasm-bindgen olmadan Rust kullanamazsınız çünkü bu platformlar JavaScript API’larını expose ediyor. Dolayısıyla bu araç zincirini öğrenmek sadece tarayıcı uygulamaları için değil, modern edge altyapısı için de temel bir beceri haline geliyor.
