Rust Async: Pemrograman Asinkron dengan async/await

Published 2026-06-16 02:45 864 words 5 min read ... Page views

This post is not yet available in English. Showing the original.
Panduan lengkap asynchronous programming di Rust: futures, async/await, Tokio runtime, concurrent tasks, channels, dan select.

Rust mendukung pemrograman asinkron melalui async/await, sebuah fitur yang memungkinkan kamu menulis kode non-blocking yang terlihat seperti kode sinkron biasa. Artikel ini membawa kamu dari konsep dasar futures hingga praktik concurrent tasks dengan Tokio.

1. Apa Itu Async di Rust?

Di Rust, fungsi async tidak langsung berjalan — ia mengembalikan sebuah Future. Future adalah nilai yang merepresentasikan komputasi yang belum selesai. Future baru dieksekusi ketika di-await atau dijalankan oleh runtime.

async fn hello() {
    println!("Hello dari async!");
}

fn main() {
    let future = hello();  // Future dibuat, tapi belum berjalan
    // Kode di sini belum mencetak apa pun
    
    // Untuk menjalankan, butuh runtime atau block_on
}

2. Menjalankan Future dengan block_on

Cara termudah menjalankan future adalah menggunakan futures::executor::block_on:

use futures::executor::block_on;

async fn hello() {
    println!("Hello, async world!");
}

fn main() {
    let future = hello();
    block_on(future);  // blokir sampai future selesai
}

Tambahkan dependency di Cargo.toml:

[dependencies]
futures = "0.3"

3. async/await dalam Praktik

await digunakan di dalam fungsi async lain untuk menunggu future selesai tanpa memblokir thread:

use std::time::Duration;
use async_std::task;

async fn tarea_satu() {
    task::sleep(Duration::from_secs(1)).await;
    println!("Tarea satu selesai");
}

async fn tarea_dua() {
    task::sleep(Duration::from_secs(2)).await;
    println!("Tarea dua selesai");
}

async fn run_both() {
    tarea_satu().await;
    tarea_dua().await;
}

fn main() {
    task::block_on(run_both());
}

Perhatikan: kode di atas berjalan secara berurutan, bukan paralel. Total waktu = 1 + 2 = 3 detik.

4. Menjalankan Task Secara Concurrent

Untuk menjalankan task secara bersamaan, gunakan join! atau futures::future::join:

use futures::join;
use std::time::Duration;
use async_std::task;

async fn fetch_user(id: u32) -> String {
    task::sleep(Duration::from_secs(1)).await;
    format!("User {}", id)
}

async fn fetch_order(id: u32) -> String {
    task::sleep(Duration::from_secs(1)).await;
    format!("Order {}", id)
}

async fn get_data() {
    let user_future = fetch_user(1);
    let order_future = fetch_order(101);
    
    let (user, order) = join!(user_future, order_future);
    println!("{} dan {} diambil bersamaan", user, order);
    // Total waktu ~1 detik, bukan 2 detik!
}

fn main() {
    async_std::task::block_on(get_data());
}

5. Tokio: Runtime Produksi untuk Rust

Tokio adalah runtime async paling populer di Rust. Ia menyediakan thread pool, timer, I/O, dan channel.

[dependencies]
tokio = { version = "1", features = ["full"] }
use tokio::time::{sleep, Duration};

async fn task_a() {
    sleep(Duration::from_secs(1)).await;
    println!("Task A selesai");
}

async fn task_b() {
    sleep(Duration::from_secs(1)).await;
    println!("Task B selesai");
}

#[tokio::main]
async fn main() {
    let start = std::time::Instant::now();
    
    tokio::join!(task_a(), task_b());
    
    println!("Waktu total: {:?}", start.elapsed());
    // ~1 detik, bukan 2 detik!
}

6. Spawning Task

tokio::spawn membuat task baru yang berjalan secara independen:

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let handle1 = tokio::spawn(async {
        sleep(Duration::from_secs(1)).await;
        println!("Spawn 1 selesai");
        42
    });
    
    let handle2 = tokio::spawn(async {
        sleep(Duration::from_secs(2)).await;
        println!("Spawn 2 selesai");
        "hello"
    });
    
    let result1 = handle1.await.unwrap();
    let result2 = handle2.await.unwrap();
    
    println!("Hasil: {} dan {}", result1, result2);
}

7. Channels untuk Komunikasi antar Task

use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(100); // buffer 100
    
    let tx2 = tx.clone();
    
    tokio::spawn(async move {
        for i in 0..5 {
            tx.send(format!("Pesan dari A: {}", i)).await.unwrap();
        }
    });
    
    tokio::spawn(async move {
        for i in 5..10 {
            tx2.send(format!("Pesan dari B: {}", i)).await.unwrap();
        }
    });
    
    drop(tx); // tutup sender agar receiver tahu kapan selesai
    
    while let Some(msg) = rx.recv().await {
        println!("Diterima: {}", msg);
    }
    
    println!("Channel selesai");
}

8. Select: Menunggu Banyak Future

tokio::select! menunggu beberapa future dan menangani yang selesai pertama:

use tokio::time::{sleep, Duration};
use tokio::sync::oneshot;

#[tokio::main]
async fn main() {
    let (tx, rx) = oneshot::channel();
    
    tokio::spawn(async move {
        sleep(Duration::from_secs(2)).await;
        tx.send("Data dari task").unwrap();
    });
    
    tokio::select! {
        result = rx => {
            match result {
                Ok(msg) => println!("Diterima: {}", msg),
                Err(_) => println!("Sender dropped"),
            }
        }
        _ = sleep(Duration::from_secs(3)) => {
            println!("Timeout!");
        }
    }
}

9. Concurrent Stream Processing

use tokio::time::{sleep, Duration};

async fn download_file(id: u32) -> String {
    sleep(Duration::from_millis(500)).await;
    format!("File-{}.pdf", id)
}

#[tokio::main]
async fn main() {
    let ids = vec![1, 2, 3, 4, 5];
    
    // Sequential (lambat)
    for id in &ids {
        let file = download_file(*id).await;
        println!("Downloaded: {}", file);
    }
    
    println!("--- Sekarang concurrent ---");
    
    // Concurrent dengan FuturesUnordered (cepat)
    let mut tasks = tokio::task::JoinSet::new();
    for id in ids {
        tasks.spawn(download_file(id));
    }
    
    while let Some(res) = tasks.join_next().await {
        println!("Downloaded: {}", res.unwrap());
    }
}

10. Async Trait

Trait dengan method async memerlukan crate tambahan seperti async-trait:

[dependencies]
async-trait = "0.1"
use async_trait::async_trait;

#[async_trait]
trait Fetcher {
    async fn fetch(&self, url: &str) -> String;
}

struct HttpFetcher;

#[async_trait]
impl Fetcher for HttpFetcher {
    async fn fetch(&self, url: &str) -> String {
        format!("Fetched from {}", url)
    }
}

#[tokio::main]
async fn main() {
    let fetcher = HttpFetcher;
    let result = fetcher.fetch("https://rust-lang.org").await;
    println!("{}", result);
}

Kesimpulan

Async programming di Rust menawarkan performa tinggi dengan overhead minimal. Dengan async/await, kamu bisa menulis kode non-blocking yang bersih dan mudah dibaca. Tokio menyediakan ekosistem lengkap: runtime, I/O, timer, channel, dan utility untuk concurrent programming.

Hal yang perlu diingat:

async fn mengembalikan Future, bukan langsung berjalan
.await menunggu future di dalam konteks async
tokio::join! untuk menjalankan task concurrent
tokio::spawn untuk task yang berjalan independen
tokio::select! untuk menangani event yang selesai pertama
• Channel untuk komunikasi antar task

Mulailah dengan contoh sederhana, lalu tingkatkan ke aplikasi yang lebih kompleks. Async Rust sangat powerful untuk web server, proxy, game server, dan sistem real-time. Selamat eksplorasi! 🦀

If you enjoyed this, leave a comment~

... Page views
© 2024 - 2026 k1m @erlk1m
Powered by theme astro-koharu · Inspired by Shoka