Chapter sebelumnya menutup dengan janji tugas ringan berbentuk state machine, dan async fn adalah pintu masuk pembuatannya. Kata kunci async di depan fn mengubah makna fungsi secara fundamental: fungsi tersebut tidak lagi menjalankan isinya saat dipanggil, melainkan menghasilkan sebuah Future — janji hasil yang akan tersedia nanti. Memanggil fungsi async menjadi semurah membuat struct; biaya sebenarnya baru muncul ketika future dijalankan oleh executor. Chapter ini membedah perilaku malas tersebut, menyiapkan proyek dengan tokio, lalu membuktikan bahwa Rust menolak async fn main tanpa penggerak.
Menandai Fungsi sebagai Asinkron #
Sintaksnya sendiri tidak membutuhkan kosakata baru selain kata async sebelum fn. Perubahan besar terjadi pada nilai kembali:
async fn fetch_data() -> String {
String::from("data penjualan kuartal")
}Tanda tangan -> String pada fungsi biasa berarti “pemanggil menerima String langsung”. Pada async fn, tanda tangan yang sama dibaca “pemanggil menerima future yang kelak menghasilkan String”. Secara konsep, kompilator menerjemahkannya kira-kira menjadi:
fn fetch_data() -> impl Future<Output = String>Fungsi async juga menerima parameter dengan aturan yang sama seperti fungsi biasa: kepemilikan dan pinjaman data tetap mengikuti tanda tangan parameternya, dan hanya nilai kembalian yang berubah makna. Satu-satunya larangan tambahan bersifat praktis: parameter berupa reference ke data lokal pemanggil bisa mempersulit pemakaian future pada task terpisah — tema yang akan kita hadapi di Chapter Spawn Task.
Tipe String berpindah posisi menjadi parameter Output pada trait Future. Trait itu sendiri bentuknya ringkas:
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}Mari kita baca kontraknya. Metode poll adalah satu-satunya cara future diajak bicara: pemanggil (executor) bertanya “sudah selesai atau belum?”, dan jawabannya bertipe Poll — Poll::Ready(nilai) berarti hasil sudah siap di tangan, Poll::Pending berarti “belum, tanyakan lagi nanti”. Parameter Pin<&mut Self> memastikan state machine tidak berpindah alamat memori selama berjalan; kita cukup tahu bahwa mekanisme itu ada tanpa membedahnya lebih jauh di section ini. Ketika tubuh fetch_data berhenti di tengah jalan (misalnya menunggu jaringan), seluruh variabel lokalnya disimpan sebagai status di dalam state machine agar eksekusi bisa dilanjutkan dari titik berhenti saat poll berikutnya — naskah drama dari Chapter Mengapa Async kini lengkap dengan penanda halaman tempat pemeranan berhenti.
Menyiapkan Proyek dengan Tokio #
Ada satu kekosongan yang harus diisi lebih dulu: pustaka standar Rust mendefinisikan trait Future, tetapi tidak menyediakan executor untuk menjalankannya. Kita perlu runtime pihak ketiga, dan tokio adalah runtime de facto di ekosistem Rust. Buat proyek baru lalu tambahkan tokio ke daftar dependensi:
cargo new belajar-async
cd belajar-asyncKemudian lengkapi Cargo.toml menjadi berikut:
[package]
name = "belajar-async"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }Notasi version = "1" berarti “versi 1.x terbaru yang kompatibel” — cargo memilih dan mengunci versinya saat kompilasi pertama. Fitur "full" mengaktifkan seluruh modul tokio (runtime, timer, filesystem, jaringan, makro) supaya kita leluasa bereksperimen; Chapter Runtime tokio akan membedah fitur-fitur tersebut satu per satu beserta alasan menyalanya untuk produksi.
Future Itu Malas #
Sifat paling mengejutkan bagi pemula: memanggil async fn tidak menjalankan apapun. Program berikut membuktikannya dengan urutan cetak yang bisa kita cek baris demi baris:
async fn fetch_data() -> String {
println!("fetch_data mulai berjalan");
String::from("data penjualan kuartal")
}
fn main() {
let future = fetch_data();
println!("future dibuat, tubuh fungsi belum berjalan");
let runtime = tokio::runtime::Runtime::new().unwrap();
let result = runtime.block_on(future);
println!("hasil: {}", result);
}Output yang Dihasilkan:
future dibuat, tubuh fungsi belum berjalan
fetch_data mulai berjalan
hasil: data penjualan kuartalUrutan output adalah kunci pembuktian. Mari kita bedah:
- Baris 1 (
async fn fetch_data() -> String): kataasyncmembuat pemanggilan fungsi ini menghasilkan future, bukan menjalankan tubuhnya. - Baris 7 (
let future = fetch_data();): baris ini hanya membangun state machine dan mengikatnya ke variabelfuture— tidak ada satu baris pun tubuh fungsi yang tereksekusi. Buktinya: cetakan “fetch_data mulai berjalan” belum muncul padahal baris berikutnya langsung mencetak penanda. - Baris 8 (
println!("future dibuat, ...")): penanda ini tercetak lebih dulu, membuktikan pemanggilanasync fnsemurah membuat variabel biasa. - Baris 10 (
let runtime = tokio::runtime::Runtime::new().unwrap();):Runtime::new()membangun executor tokio lengkap dengan thread worker-nya.unwrap()mengambil runtime dariResultkarena pembangunan bisa gagal di lingkungan ekstrem; kita bedah runtime ini tuntas di Chapter Runtime tokio. - Baris 11 (
let result = runtime.block_on(future);):block_onmengambil kepemilikan future lalu mendorongnya sampai selesai — threadmainmenunggu di sini (blocking) sementara executor meng-poll future. Barulah cetakan “fetch_data mulai berjalan” muncul, dan nilaiStringdihasilkan. - Baris 13 (
println!("hasil: {}", result);): nilai dariresultadalahStringbiasa — setelah future selesai, kita bekerja dengan data seperti fungsi sinkron pada umumnya.
Perlu dicatat juga bahwa setiap pemanggilan fetch_data() membangun future baru yang mandiri: memanggil fungsi itu dua kali menghasilkan dua state machine terpisah, bukan dua pegangan atas janji yang sama. Future dengan demikian adalah nilai biasa — bisa diikat ke variabel, dipindahkan ke fungsi lain, dan dijalankan kapan pun kita putuskan.
main Tidak Boleh async #
Pemula umumnya mencoba jalan pintas: membuat main itu sendiri async agar bisa memanggil fungsi async langsung. Rust menolak mentah-mentah:
async fn main() {
println!("Halo dari async main");
}Pesan galat kompilator:
error[E0752]: `main` function is not allowed to be `async`
--> src/main.rs:1:1
|
1 | async fn main() {
| ^^^^^^^^^^^^^^^ `main` function is not allowed to be `async`
For more information about this error, try `rustc --explain E0752`.
error: could not compile `belajar-async` (bin "belajar-async") due to 1 previous errorGalat E0752 masuk ke akar masalahnya. async fn main menghasilkan future, dan future butuh executor untuk berjalan — sementara main justru titik masuk paling awal tempat executor itu seharusnya dibangun. Tidak ada yang bisa menggerakkan future main jika main sendirilah yang harus membangun penggeraknya; kondisi ayam dan telur ini ditutup rapat oleh kompilator. Jalan keluarnya dua macam: menunggu nilai future secara manual seperti pola Runtime::new + block_on di atas, atau memakai makro #[tokio::main] yang menulis pola tersebut untuk kita — materi Chapter Runtime tokio.
Ringkasan #
async fn mengubah fungsi menjadi pabrik Future: pemanggilan hanya membangun state machine malas, dan trait Future berkontrak lewat poll yang menjawab Ready atau Pending. Program pembuktian memperlihatkan urutan cetak terbalik dari intuisi — penanda dibuat sebelum tubuh fungsi berjalan — karena eksekusi baru terjadi ketika block_on menggerakkan future di atas runtime tokio. Percobaan async fn main dijawab E0752: titik masuk program wajib sinkron karena dialah yang membangun executor. Dengan future kini di tangan, pertanyaan berikutnya adalah bagaimana menunggu hasilnya tanpa memblok thread — tugas operator .await di chapter selanjutnya.