# Rust'ta Async/Await: Tokio, Futures ve Asenkron Eszamanlilik Rehberi > Rust'ta async/await ile asenkron programlama rehberi. Tokio runtime, Future trait, channel yapilari, semaphore ile hiz sinirlandirma, spawn_blocking ve hata yonetimi. Uretim ortamina yonelik kod ornekleri ve mulakat sorularina hazirlik. - Published: 2026-04-30 - Updated: 2026-04-30 - Author: SharpSkill - Tags: rust, async, tokio, futures, concurrency - Reading time: 12 min --- Asenkron programlama, Rust dilinin en guclu ve ayni zamanda en incelikli ozelliklerinden birini olusturmaktadir. Bircok modern programlama dili async/await yapisini thread havuzlari veya event loop mekanizmalari uzerinde soyutlama olarak sunarken, Rust temel olarak farkli bir yol izlemektedir: Rust'ta Future yapilar sifir maliyetli soyutlamalardir (zero-cost abstractions), derleme zamaninda cozumlenir ve calisma zamaninda ek yuk olusturmaz. Ownership sistemi, borrow checker ve poll tabanli Future modeli bir araya geldiginde, Rust'un asenkron ekosistemi sistem programlama dilleri arasinda benzersiz bir konuma yerlesir. Bu makalede Rust'ta async/await mekanizmasinin teknik temelleri ele alinmakta, Tokio runtime fiili standart olarak incelenmekte ve channel yapilari, semaphore ile hiz sinirlandirma, spawn_blocking gibi ileri duzey desenler ayrintili olarak islenmektedir. Tum kavramlar, hem teknik mulakatlarda hem de uretim kodunda dogrudan uygulanabilir pratik orneklerle desteklenmektedir. > **Temel Kavram: Tembel (Lazy) Future Yapilari** > > JavaScript Promise yapilari olusturuldugu anda calistirilirken, Rust Future yapilari dogasi geregi tembeldir. Bir Future, aktif olarak poll edilmedigi surece hicbir kod calistirmaz. Yalnizca bir Future olusturmak hicbir hesaplamayi baslatmaz; `.await` cagrisi yapilmadan veya Tokio gibi bir runtime'a teslim edilmeden yurutme gerceklesmez. Bu davranis, Rust'taki asenkron mimarinin tamamini anlamanin temelini olusturmaktadir. ## Rust Future Yapisinin Diger Dillerden Farki Rust'ta Future modeli, standart kutuphanedeki `Future` trait'ine dayanmaktadir. Rust'taki her asenkron ifade bu trait'i uygulamaktadir ve trait, tek bir metot tanimlar: `poll`. Runtime, Future hazir bir deger dondurunceye kadar `poll` metodunu tekrar tekrar cagirmaktadir. ```rust // core::future::Future trait Future { type Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; } ``` Donus tipi olan `Poll`, iki varyanta sahip bir enum'dur: `Poll::Ready(value)` Future'in tamamlandigini ve `value` degerini dondurdugunu belirtir. `Poll::Pending` ise Future'in henuz hazir olmadigini ve daha sonra yeniden poll edilmesi gerektigini ifade eder. `Context` parametresi bir `Waker` icerir; bu yapi, Future'in harici bir olay gerceklestiginde (ornegin bir ag isleminin tamamlanmasi) runtime'i yeniden poll etmesi gerektigine dair bilgilendirmesini saglar. Bu mekanizma, JavaScript ve Python'dan temelden ayrilmaktadir. JavaScript'te bir Promise olusturuldugu anda baslatilir ve arka planda event loop uzerinde calisir. Python asyncio'da coroutine'ler olusturulduklarinda benzer sekilde planlanir. Rust Future yapilari ise bir runtime tarafindan acikca poll edilene kadar hicbir islem gerceklestirmez. Bu tasarim, gizli yan etkileri ortadan kaldirir ve derleyiciye Future'leri heap tahsisi veya dinamik dispatch gerektirmeyen durum makinelerine (state machine) optimize etme imkani tanir. `Pin<&mut Self>` parametresi, Future poll edilirken bellekte tasinmamasini garanti eder. Bu gereklilik, derleyicinin async bloklari kendi alanlarindan referanslar icerebilen durum makinelerine donusturmesinden kaynaklanmaktadir. `Pin` olmadan, bu oz-referanslar (self-references) bellekte tasima sonrasinda gecersiz adreslere isaret edebilir. ## Tokio Runtime Kurulumu ve Ilk Asenkron Fonksiyon Rust, standart kutuphanesinde bir asenkron runtime sunmamaktadir. Bu tasarim bilingli olarak moduler tutulmustur: dil cekirdegi `async`, `await` ve `Future` trait'ini saglarken, fiili yurutme harici bir runtime'a devredilmektedir. Tokio, Rust uretim sistemlerinin buyuk cogulugunda kullanilan fiili standart runtime olarak one cikmaktadir. Kurulum `Cargo.toml` dosyasi uzerinden gerceklestirilir: ```toml # Cargo.toml [dependencies] tokio = { version = "2", features = ["rt-multi-thread", "macros", "net", "time"] } ``` `rt-multi-thread` ozelligi, isi birden fazla isletim sistemi thread'ine dagitan cok-thread'li runtime'i aktive eder. `macros`, `#[tokio::main]` niteliginin kullanimini mumkun kilar ve bu nitelik `main` fonksiyonunu asenkron bir fonksiyona donusturur. `net` ve `time` ise asenkron ag ve zamanlayici islemlerini saglamaktadir. Bu yapilandirmayla ilk asenkron Rust uygulamasi yazilabilir: ```rust // main.rs #[tokio::main] async fn main() { let result = fetch_data().await; println!("Got: {result}"); } async fn fetch_data() -> String { // Simulate async I/O tokio::time::sleep(std::time::Duration::from_millis(100)).await; String::from("data loaded") } ``` `#[tokio::main]` makrosu arka planda senkron bir `main` fonksiyonu olusturur, bir Tokio runtime baslatir ve asenkron bloku bu runtime uzerinde calistirir. `fetch_data()` bir Future dondurur ve bu Future ancak `.await` cagrildiginda yurutulur. `.await` noktasi ayni zamanda bir yield noktasidir: zamanlayici calisirken runtime ayni thread uzerinde baska gorevleri calistirabilir. ## Gorev Olusturma ve Yapilandirilmis Eszamanlilik `.await` sirali asenkron yurutme saglarken, `tokio::spawn` bagimsiz gorevlerin paralel olarak calistirilmasini mumkun kilar. Her spawn edilen gorev, Tokio runtime uzerinde bagimsiz bir birim olarak calisir ve mevcut herhangi bir worker thread tarafindan yurutulebilir. ```rust // concurrent_tasks.rs use tokio::task::JoinHandle; #[tokio::main] async fn main() { // Spawn two independent tasks let handle_a: JoinHandle = tokio::spawn(async { expensive_computation("dataset_a").await }); let handle_b: JoinHandle = tokio::spawn(async { expensive_computation("dataset_b").await }); // Await both results let (result_a, result_b) = ( handle_a.await.expect("task A panicked"), handle_b.await.expect("task B panicked"), ); println!("Results: {result_a}, {result_b}"); } async fn expensive_computation(name: &str) -> u32 { tokio::time::sleep(std::time::Duration::from_secs(1)).await; println!("{name} done"); 42 } ``` `tokio::spawn`, calisan goreve bir tutamac (handle) gorevi goren `JoinHandle` dondurur. Bu handle uzerinde `.await` cagrildiginda, cagrici kod sonucu bekler. Her iki gorev runtime uzerinde esanli olarak calistigi icin, yukaridaki ornekte her hesaplama bir saniye surse de toplam calisma suresi yalnizca yaklasik bir saniyedir. `handle.await` donus degeri `Result` tipindedir. `JoinError`, spawn edilen gorev panic yaptiginda veya runtime kapatildiginda ortaya cikar. Ornekteki `.expect()` kullanimi bu hatalardan birini panic'e donusturur; uretim kodunda bu kisim uygun hata yonetimiyle degistirilmelidir. > **tokio::spawn icin Send + 'static Gereksinimleri** > > `tokio::spawn`'a verilen her Future, `Send` ve `'static` trait'lerini karsilamalidir. `Send` gereklidir cunku Tokio gorevleri worker thread'ler arasinda tasiyabilir. `'static`, Future'in sinirli yasam sureli referanslar icermemesi gerektigini ifade eder cunku gorev, cagricisindan daha uzun yasayabilir. Yerel degiskenlere referanslarin spawn edilen gorevlere aktarilmasi sik karsilasilan bir hataya yol acar. Cozum, verileri klonlamak veya async bloka tasinmadan once `Arc` ile sarmaktir. ## tokio::join! ve tokio::select! ile Future Birlestirme `tokio::spawn` disinda Tokio, acik gorev olusturma gerektirmeden yapilandirilmis eszamanlilik saglayan iki makro sunmaktadir: `tokio::join!` ve `tokio::select!`. `tokio::join!` birden fazla Future'i ayni gorev uzerinde esanli olarak calistirir ve tumunun tamamlanmasini bekler. Birden fazla bagimsiz veri kaynaginin paralel olarak sorgulanmasi gereken senaryolar icin idealdir: ```rust // join_example.rs use tokio::time::{sleep, Duration}; #[tokio::main] async fn main() { // All three run concurrently, total time ~200ms (not 600ms) let (users, orders, inventory) = tokio::join!( fetch_users(), fetch_orders(), fetch_inventory() ); println!("Users: {}, Orders: {}, Stock: {}", users.len(), orders.len(), inventory); } async fn fetch_users() -> Vec { sleep(Duration::from_millis(200)).await; vec!["Alice".into(), "Bob".into()] } async fn fetch_orders() -> Vec { sleep(Duration::from_millis(150)).await; vec!["ORD-001".into()] } async fn fetch_inventory() -> u32 { sleep(Duration::from_millis(100)).await; 84 } ``` Uc Future da ayni gorev icinde calistigi icin toplam sure en yavas Future tarafindan belirlenir: sirali yurutmedeki 450 milisaniye yerine 200 milisaniye. `tokio::spawn`'dan farkli olarak `join!` icindeki Future'ler `Send` veya `'static` gerektirmez; bu da yerel degiskenlere referanslarla kullanimi kolaylastirir. `tokio::select!` ise ilk tamamlanan Future'i bekler ve digerlerini iptal eder. Zaman asimi desenleri, onbellek yedekleme mekanizmalari veya iptal mantigi icin kullanislidir: ```rust // select_example.rs use tokio::time::{sleep, Duration}; #[tokio::main] async fn main() { tokio::select! { val = fetch_from_cache() => { println!("Cache hit: {val}"); } val = fetch_from_database() => { println!("DB result: {val}"); } } } async fn fetch_from_cache() -> String { sleep(Duration::from_millis(5)).await; "cached_value".into() } async fn fetch_from_database() -> String { sleep(Duration::from_millis(50)).await; "db_value".into() } ``` Bu ornekte onbellek erisimi 5 milisaniyede tamamlandigi icin kazanir; veritabani sorgusu 50 milisaniye gerektirmektedir. Veritabani erisimi otomatik olarak iptal edilir (drop edilir). `select!` Future'leri ayri thread'lerde calistirmaz; bunun yerine ayni gorev uzerinde sirali olarak poll eder ve ilk tamamlanani secer. ## Asenkron Rust Kodunda Hata Yonetimi Asenkron Rust'ta hata yonetimi, senkron koddakiyle ayni ilkeleri takip eder: `Result` ve `?` operatoru. Asenkron islemler genellikle farkli hata kaynaklariyla (ag, deserialization, dosya sistemi) etkilestigi icin, ozel bir hata enum'u tanimlamak onerilen yaklasimdir. ```rust // error_handling.rs use std::io; #[derive(Debug)] enum AppError { Network(reqwest::Error), Parse(serde_json::Error), Io(io::Error), } async fn load_config(url: &str) -> Result { let response = reqwest::get(url) .await .map_err(AppError::Network)?; let text = response.text() .await .map_err(AppError::Network)?; let config: Config = serde_json::from_str(&text) .map_err(AppError::Parse)?; Ok(config) } #[derive(serde::Deserialize)] struct Config { db_url: String, port: u16, } ``` `.map_err(AppError::Variant)?` deseni, belirli hata tipini kendi hata varyantina donusturur ve ust katmana iletir. Bu yaklasim Rust'ta idiomatiktir ve senkron ile asenkron kodda ayni sekilde calisir. `?` operatoru mevcut fonksiyonu erken sonlandirir ve hatayi dondurur; bu sayede ic ice match ifadeleri onlenir. Daha buyuk projelerde `thiserror` crate'i otomatik `From` uygulamalari saglar ve acik `map_err` olmadan `?` operatorunun kullanilmasina olanak tanir. Prototiplerde ve betiklerde ise her turlu hatayi kabul eden evrensel bir hata tipi olarak `anyhow::Result` tercih edilebilir. ## Asenkron Iletisim icin Channel Yapilari Gorevlerin birbirleriyle iletisim kurmasi gerektiginde, Tokio channel yapilari guvenli ve verimli bir cozum sunmaktadir. `mpsc` channel (Multiple Producer, Single Consumer), asenkron gorevler arasindaki iletisim icin en yaygin kullanilan desendir. ```rust // channel_example.rs use tokio::sync::mpsc; #[tokio::main] async fn main() { // Bounded channel with capacity 32 let (tx, mut rx) = mpsc::channel::(32); // Producer task let producer = tokio::spawn(async move { for i in 0..5 { tx.send(format!("message-{i}")).await.unwrap(); tokio::time::sleep(std::time::Duration::from_millis(10)).await; } // tx dropped here, closing the channel }); // Consumer reads until channel closes while let Some(msg) = rx.recv().await { println!("Received: {msg}"); } producer.await.unwrap(); } ``` `mpsc::channel` 32 kapasiteyle olusturulmaktadir; bu, gondericinin bloke olmasindan once tamponda 32 mesajin bekleyebilecegi anlamina gelir. Gonderici (tx) drop edildiginde, aliciya (rx) baska mesaj gelmeyecegini bildirir ve `rx.recv()` `None` dondurur. Bu desen, tuketici dongusunun temiz bir sekilde sonlandirilmasini saglar. Tokio, `mpsc` disinda tek bir mesaj icin `oneshot`, birden fazla tuketici icin `broadcast` ve en guncel degerin dagitimi icin `watch` channel tiplerini de sunmaktadir. Channel tipinin secimi, iletisim desenine dogrudan bagli olup performans ve bellek tuketimini etkiler. ## Gercek Dunya Deseni: Semaphore ile Hiz Sinirli Eszamanli HTTP Pratik uygulamalarda asenkron sistemlerin genellikle esanli islem sayisini sinirlamasi gerekmektedir; gerek harici API'lerin asiri yuklenmesini onlemek gerekse veritabani baglantilari gibi kaynaklari korumak icin. Tokio'nun `Semaphore` yapisi bu sorun icin zarif bir cozum sunmaktadir. ```rust // rate_limited_fetcher.rs use std::sync::Arc; use tokio::sync::Semaphore; async fn fetch_all(urls: Vec, max_concurrent: usize) -> Vec> { let semaphore = Arc::new(Semaphore::new(max_concurrent)); let mut handles = Vec::new(); for url in urls { let sem = Arc::clone(&semaphore); let handle = tokio::spawn(async move { // Acquire permit before making request let _permit = sem.acquire().await.unwrap(); reqwest::get(&url) .await .map(|r| r.status().to_string()) .map_err(|e| e.to_string()) // permit dropped here, allowing next task to proceed }); handles.push(handle); } let mut results = Vec::new(); for handle in handles { results.push(handle.await.unwrap()); } results } ``` Semaphore, `max_concurrent` sayida izin (permit) ile baslatilir. Her gorev, asil istegi gerceklestirmeden once bir izin edinmelidir. Tum izinler dagitildiginda, `sem.acquire().await` baska bir gorev iznini serbest birakana kadar asenkron olarak bekler. Izin, `_permit` degiskeni kapsam disindan ciktiginda otomatik olarak serbest birakilir; bu durum Rust'un RAII mekanizmasi tarafindan garanti edilmektedir. Bu desen; web scraping, API toplama ve binlerce istegin esanli olarak baslatilabilecegi ancak yalnizca sinirli sayidanin aktif olmasi gereken toplu islemler icin ozellikle faydalidir. > **Eszamanlilik Ilkeli Secimi** > > `join!`, `spawn` ve semaphore arasindaki secim, kullanim senaryosuna baglidir. `tokio::join!`, birlikte tamamlanmasi gereken bilinen, az sayida Future icin uygundur. `tokio::spawn`, cok-thread'li yurutmeden fayda saglayan bagimsiz gorevler icin dogru secimdir. Semaphore'lar `spawn` ile eszamanlilik ust sinirini birlestirir. Basit zaman asimi mantigi veya onbellek yedekleme mekanizmalari icin `tokio::select!` uygun semantigi saglar. Bu ilkellerin kombinasyonu, karmasik asenkron mimarilerin hassas bir sekilde modellenmesini mumkun kilmaktadir. ## Pin ve Unpin: Future Yapilari icin Bellek Guvenligi `Pin` ve `Unpin` tipleri, asenkron Rust ekosisteminde en sik yanlis anlasilan kavramlar arasinda yer almaktadir. Amaci, derleyicinin async bloklari durum makinelerine donustururken olusturdugu oz-referansli yapilanin (self-referential struct) bellek guvenligini garanti etmektir. Bir async blok, yerel bir degiskene referansi bir `.await` noktasi uzerinden tasiyorsa, derleyici hem degiskeni hem de ona olan referansi alan olarak iceren bir durum makinesi olusturur. Bu durum makinesi bellekte tasinirsa, referans gecersiz bir adrese isaret eder. `Pin<&mut Self>` tam olarak bunu onler: sabitlenmis (pinned) bir deger artik tasinamaz. Cogu gelistirici `Pin` ile dogrudan etkilesime girmez cunku derleyici ve `.await` sozdizimi ayrintilari soyutlar. Acik sabitlemenin (explicit pinning) gerekli hale geldigi durumlar sunlardir: `Future` trait'inin manuel uygulanmasi, dinamik dispatch edilen Future'ler icin `Box::pin` kullanimi ve `select!` ifadelerinde `pin!` makrolarinin kullanimi. `Unpin` uygulayan tipler sabitlenmis olsalar bile serbestce tasinabilir. Rust'taki standart tiplerin cogu `Unpin`'i otomatik olarak uygulamaktadir. ## Performans Ozellikleri ve spawn_blocking Tokio'nun cok-thread'li runtime'i varsayilan olarak makinedeki CPU cekirdegi sayisi kadar worker thread kullanir. Her worker thread kendi event loop'unu isletir ve yuku esit dagitmak icin diger thread'lerden gorev calabilir (work-stealing). Asenkron gorevler, worker thread'ler uzerinde uzun sureli CPU-yogun hesaplamalar yapmamadir cunku bu, ayni thread'deki diger gorevleri bloke edecektir. CPU-yogun islemler icin Tokio, kodu ozellikle bloke eden islemler icin ayrilan ayri bir thread havuzunda calistiran `spawn_blocking` sunmaktadir: ```rust // spawn_blocking_example.rs #[tokio::main] async fn main() { let hash = tokio::task::spawn_blocking(|| { // CPU-intensive work runs on a blocking thread compute_hash(b"large dataset") }) .await .unwrap(); println!("Hash: {hash}"); } fn compute_hash(data: &[u8]) -> String { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); data.hash(&mut hasher); format!("{:x}", hasher.finish()) } ``` `spawn_blocking`, verilen closure'u varsayilan olarak 512 thread'e kadar genisleyebilen ozel bir bloke eden thread havuzuna tasir. Donus degeri, asenkron olarak beklenebilen bir `JoinHandle`'dir ve worker thread'leri bloke etmez. Bu desen; parola hashleme, goruntu isleme, sikistirma veya birkac mikrosaniyeden uzun suren herhangi bir hesaplama icin zorunludur. Temel kural su sekildedir: 10-100 mikrosaniyeden fazla CPU zamani gerektiren her islem `spawn_blocking` icinde calistirilmalidir. Tokio'nun asenkron alternatifleri (`tokio::fs`) uzerinden gerceklestirilmeyen senkron dosya sistemi islemleri de ayni sekilde `spawn_blocking` icinde yer almalidir. ## Sonuc Rust'ta asenkron programlama, en yuksek performansi tip sisteminin guvenlik garantileriyle birlestirmektedir. Temel cikarimlar asagidaki sekilde ozetlenebilir: - Rust Future yapilari tembel (lazy) olup yalnizca poll edildiginde kod calistirir; bu ozellik onlari JavaScript Promise ve Python coroutine yapilarindan temelden ayirir - `Future` trait'i ve `poll` metodu, tum rust async await ekosisteminin temelini olusturur ve heap tahsisi gerektirmeyen sifir maliyetli soyutlamalari mumkun kilar - Tokio, asenkron Rust icin standart runtime olup cok-thread'li zamanlama, zamanlayicilar, ag I/O ve senkronizasyon ilkelleri saglar - `tokio::spawn`, `Send + 'static` gereksinimleriyle bagimsiz gorevler olustururken, `tokio::join!` bu kisitlamalar olmaksizin birden fazla Future'i ayni gorev icinde calistirir - `tokio::select!` ilk tamamlanan Future'i bekler ve digerlerini iptal eder; zaman asimi ve yedekleme desenleri icin idealdir - Hata yonetimi, senkron Rust koduyla ayni sekilde `Result` ve `?` operatorunu kullanir; farkli hata kaynaklarinin toplanmasi icin ozel hata enum'lari tavsiye edilir - Channel yapilari (`mpsc`, `oneshot`, `broadcast`, `watch`) gorevler arasinda farkli semantiklerle guvenli iletisim saglar - Semaphore'lar eszamanliligi sinirlandirarak kontrolllu izin dagitimi ile harici kaynaklarin asiri yuklenmesini onler - `Pin`, oz-referansli Future'lerin bellek guvenligini garanti eder ve cogu durumda derleyici tarafindan otomatik olarak yonetilir - `spawn_blocking`, CPU-yogun veya senkron bloke eden islemleri asenkron worker thread'leri engellemeden ayri bir thread havuzuna aktarir - Bu kavramlarin anlasilmasi hem rust concurrency interview icin hem de yuksek performansli uretim sistemlerinin gelistirilmesi icin vazgecilmezdir --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/tr/blog/rust/rust-async-await-tokio-futures-concurrency