async block
本集目標
學會用 async { ... } 在函數裡當場做出一個 Future,並理解它和 async fn 的關係。
正文
當場做一個 Future
除了 async fn,Rust 還讓你用 async { ... } 在程式中間當場建立一個 Future:
extern crate tokio;
#[tokio::main]
async fn main() {
// 這個 async block 本身就是一個 Future
let fut = async {
println!("我在一個 async block 裡");
42
};
// 和 async fn 一樣,要 .await 才會真的跑
let value = fut.await;
println!("拿到 {}", value);
}
注意:和 async fn 完全一樣,光是寫出 async { ... } 不會執行裡面的程式,你只是做出一個惰性的 Future,要 .await 才會動。
async fn 和 async block 的關係
這兩者可以先用一個很粗略的角度來分:
async fn是一個具名的Future工廠——你定義一次,之後可以重複呼叫,每次呼叫產生一個新的Future。asyncblock 是當場建立的一個匿名Future——就在這裡、這一個,沒有名字。
也就是說,如果只看「具名、可重複使用」和「匿名、當場建立」這件事,它們有點像普通函數和閉包的差別。但這只是幫你抓第一印象的比喻,不要對它進行過度理解:async fn 和 async block 都產生 Future,但 async block 本身不是閉包,它不靠 () 呼叫,建立出來後是靠 .await 或 runtime 推進。
在 Result 世界,這件事不需要新語法
這裡有個很有意思的對照。在 Result 世界,如果你想要「當場來一段可以用 ? 的區塊」,其實不需要任何新語法——一個立刻呼叫的閉包就辦到了:
fn main() {
// 定義一個閉包,然後馬上用 () 呼叫它
let result: Result<i32, std::num::ParseIntError> = (|| {
let x = "3".parse::<i32>()?;
let y = "4".parse::<i32>()?;
Ok(x + y)
})();
println!("{:?}", result);
}
這裡的 (|| { ... })() 是「定義一個閉包並立刻呼叫」。閉包的函數體可以用 ?,因為這個閉包自己回傳 Result;呼叫完之後,外層的 main 只會拿到那個 Result 值。
為什麼 Future 世界不能照搬
你可能會想:那 Future 世界是不是也照抄就好?把 .await 塞進一個立刻呼叫的閉包裡?
extern crate tokio;
async fn get_number() -> i32 {
42
}
#[tokio::main]
async fn main() {
let value = (|| {
get_number().await // 編譯錯誤:普通閉包裡不能 .await
})();
}
不行。原因回到前幾集講的:.await 需要把整段程式改寫成狀態機,才能做到「暫停以允許並行」。但一個普通閉包只會被編譯成一個普通函數,裡面沒有「暫停,之後再恢復」這回事——它表達不了那種改寫。所以 Result 世界那招在這裡行不通。
這正是 async block 存在的理由。當你寫 async { ... },等於是明確地叫編譯器:「把這一塊改寫成一個 Future」。有了這個專屬語法,裡面才能合法地用 .await:
extern crate tokio;
async fn get_number() -> i32 {
42
}
#[tokio::main]
async fn main() {
let value = async {
get_number().await // 這次可以了,因為這是 async block
}.await;
println!("{}", value);
}
到這裡,前五集先把 async fn、.await、async block 這幾個基本語法和心智模型建立好了。下一集開始,我們要捲起袖子,自己動手把 Future 的內部機制拆開來看。
重點整理
async { ... }在函數中間當場建立一個匿名的Future,一樣要.await才會跑async fn是具名、可重複呼叫的Future工廠;asyncblock 是當場建立的一個匿名Future- 如果建立後立刻呼叫的閉包回傳
Result就可以用?;外層只會拿到閉包呼叫後的Result值 .await不能照搬這招,因為它只能出現在async結構裡,普通閉包做不到暫停和恢復——所以才需要asyncblock 這個專屬語法