Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Future และ Syntax ของ Async

element หลักของ asynchronous programming ใน Rust คือ future และ keyword async และ await ของ Rust

future คือค่าที่อาจไม่พร้อมตอนนี้แต่จะพร้อมในจุดในอนาคต (แนวคิด เดียวกันนี้ปรากฏในหลายภาษา บางครั้งภายใต้ชื่ออื่นเช่น task หรือ promise) Rust ให้ trait Future เป็น building block เพื่อให้ operation async ต่าง ๆ ถูก implement ด้วยโครงสร้างข้อมูลต่าง ๆ แต่ ด้วย interface ทั่วไป ใน Rust future คือ type ที่ implement trait Future แต่ละ future เก็บข้อมูลของตัวเองเกี่ยวกับความก้าวหน้าที่ทำ และ “พร้อม” หมายความว่าอะไร

คุณใช้ keyword async กับ block และฟังก์ชันเพื่อระบุว่าพวกมันถูก interrupt และ resume ได้ ภายใน block async หรือฟังก์ชัน async คุณ ใช้ keyword await เพื่อ await future (นั่นคือ รอให้มันพร้อม) ได้ จุดใดที่คุณ await future ภายใน block async หรือฟังก์ชันเป็นจุด ที่เป็นไปได้สำหรับ block หรือฟังก์ชันนั้นที่จะ pause และ resume กระบวนการของการตรวจสอบกับ future เพื่อดูว่าค่าของมันใช้ได้ยังเรียก polling

บางภาษาอื่นเช่น C# และ JavaScript ก็ใช้ keyword async และ await สำหรับ async programming ถ้าคุณคุ้นเคยกับภาษาเหล่านั้น คุณอาจสังเกต ความแตกต่างสำคัญบางอย่างในวิธีที่ Rust จัดการ syntax นั่นมีเหตุผลที่ ดี ดังที่เราจะเห็น!

เมื่อเขียน async Rust เราใช้ keyword async และ await เกือบทุก เวลา Rust คอมไพล์พวกมันเป็นโค้ดเทียบเท่าโดยใช้ trait Future เหมือน ที่มันคอมไพล์ loop for เป็นโค้ดเทียบเท่าโดยใช้ trait Iterator อย่างไรก็ตาม เพราะ Rust ให้ trait Future คุณยัง implement มัน สำหรับ type ของคุณเองเมื่อต้องการได้ ฟังก์ชันหลายตัวที่เราจะเห็น ตลอดบทนี้ return type ที่มี implementation ของ Future ของตัวเอง เราจะกลับไปยังนิยามของ trait ที่ท้ายสุดของบทและขุดเข้าไปในวิธีที่ มันทำงานเพิ่ม แต่นี่เป็นรายละเอียดเพียงพอที่จะให้เราเดินหน้าต่อ

ทั้งหมดนี้อาจรู้สึก abstract หน่อย ดังนั้นมาเขียนโปรแกรม async แรก ของเรา — web scraper เล็ก เราจะส่งสอง URL จาก command line, fetch ทั้งสองแบบ concurrent และ return ผลของอันไหนเสร็จก่อน ตัวอย่างนี้จะ มี syntax ใหม่พอควร แต่ไม่ต้องกังวล — เราจะอธิบายทุกอย่างที่คุณต้อง รู้ขณะที่เราไป

โปรแกรม Async แรกของเรา

เพื่อเก็บโฟกัสของบทนี้ที่การเรียน async ไม่ใช่การ juggle ส่วนของ ecosystem เราสร้าง crate trpl (trpl ย่อสำหรับ “The Rust Programming Language”) มัน re-export type, trait และฟังก์ชันทั้งหมด ที่คุณต้องการ ส่วนใหญ่จาก crate futures และ tokio crate futures เป็นบ้านอย่างเป็น ทางการสำหรับการทดลอง Rust สำหรับโค้ด async และมันจริง ๆ คือที่ที่ trait Future ถูกออกแบบดั้งเดิม Tokio เป็น async runtime ที่ใช้ อย่างกว้างขวางที่สุดใน Rust วันนี้ โดยเฉพาะสำหรับ web application มี runtime อื่นที่ยอดเยี่ยมอยู่ และพวกมันอาจเหมาะกับจุดประสงค์ของ คุณมากกว่า เราใช้ crate tokio ใต้ฝ่ามือสำหรับ trpl เพราะมันถูก ทดสอบดีและใช้กว้างขวาง

ในบางกรณี trpl ยังตั้งชื่อใหม่หรือ wrap API ดั้งเดิมเพื่อเก็บคุณ โฟกัสที่รายละเอียดที่เกี่ยวข้องกับบทนี้ ถ้าคุณต้องการเข้าใจว่า crate ทำอะไร เราขอแนะนำให้คุณดู source code ของมัน คุณจะเห็นได้ว่าแต่ละ re-export มาจาก crate ใด และเราได้ทิ้ง comment มากที่อธิบายว่า crate ทำอะไร

สร้างโปรเจกต์ binary ใหม่ชื่อ hello-async และเพิ่ม crate trpl เป็น dependency:

$ cargo new hello-async
$ cd hello-async
$ cargo add trpl

ตอนนี้เราใช้ชิ้นส่วนต่าง ๆ ที่ trpl ให้มาเพื่อเขียนโปรแกรม async แรกของเรา เราจะ build เครื่องมือ command line เล็กที่ fetch สอง หน้าเว็บ ดึง element <title> จากแต่ละหน้า และ print title ของหน้า ใดที่เสร็จกระบวนการทั้งหมดนั้นก่อน

นิยามฟังก์ชัน page_title

มาเริ่มโดยเขียนฟังก์ชันที่รับ URL หนึ่งหน้าเป็น parameter, ทำ request ไปยังมัน และ return text ของ element <title> (ดู Listing 17-1)

Filename: src/main.rs
extern crate trpl; // required for mdbook test

fn main() {
    // TODO: we'll add this next!
}

use trpl::Html;

async fn page_title(url: &str) -> Option<String> {
    let response = trpl::get(url).await;
    let response_text = response.text().await;
    Html::parse(&response_text)
        .select_first("title")
        .map(|title| title.inner_html())
}
Listing 17-1: นิยามฟังก์ชัน async เพื่อรับ element title จากหน้า HTML

ก่อนอื่น เรานิยามฟังก์ชันชื่อ page_title และทำเครื่องหมายมันด้วย keyword async จากนั้นเราใช้ฟังก์ชัน trpl::get เพื่อ fetch URL ใดก็ตามที่ส่งเข้าและเพิ่ม keyword await เพื่อ await response เพื่อ รับ text ของ response เราเรียกเมธอด text ของมันและ await มันอีก ครั้งด้วย keyword await ทั้งสองขั้นตอนนี้เป็น asynchronous สำหรับ ฟังก์ชัน get เราต้องรอ server ส่งคืนส่วนแรกของ response ซึ่งจะ รวม HTTP header, cookie และอื่น ๆ และส่งแยกจาก body ของ response ได้ โดยเฉพาะถ้า body ใหญ่มาก อาจใช้เวลาให้ทั้งหมดของมันมาถึง เพราะ เราต้องรอ ทั้งหมด ของ response ที่มาถึง เมธอด text ยังเป็น async

เราต้อง await future ทั้งสองนี้ชัดเจน เพราะ future ใน Rust เป็น lazy — พวกมันไม่ทำอะไรจนกว่าคุณขอพวกมันด้วย keyword await (จริง ๆ Rust จะแสดง warning ของ compiler ถ้าคุณไม่ใช้ future) นี่ อาจทำให้คุณนึกถึงการพูดถึง iterator ในส่วน “ประมวลผลชุด Item ด้วย Iterator” ในบทที่ 13 Iterator ไม่ทำอะไรยกเว้นคุณเรียกเมธอด next ของพวกมัน — ไม่ว่าโดยตรงหรือโดยใช้ loop for หรือเมธอดเช่น map ที่ใช้ next ใต้ฝ่ามือ ในทำนองเดียวกัน future ไม่ทำอะไรยกเว้นคุณขอพวกมันชัดเจน ความเป็น lazy นี้อนุญาตให้ Rust หลีกเลี่ยงการรันโค้ด async จนกว่ามัน ต้องการจริง

สังเกต — นี่ต่างจากพฤติกรรมที่เราเห็นเมื่อใช้ thread::spawn ใน ส่วน “สร้าง Thread ใหม่ด้วย spawn” ในบทที่ 16 ที่ closure ที่เราส่งไปยังอีกเธรดเริ่มรันทันที มันยัง ต่างจากวิธีที่ภาษาอื่นหลายภาษาเข้าหา async แต่มันสำคัญสำหรับ Rust ที่จะให้การรับประกัน performance ของมันได้ เช่นเดียวกับ iterator

เมื่อเรามี response_text แล้ว เรา parse มันเป็น instance ของ type Html โดยใช้ Html::parse ได้ แทน raw string ตอนนี้เรามี type ข้อมูลที่เราใช้ทำงานกับ HTML เป็นโครงสร้างข้อมูลที่ richer โดย เฉพาะ เราใช้เมธอด select_first เพื่อหา instance แรกของ selector CSS ที่ให้ โดยส่ง string "title" เราจะได้ element <title> แรก ใน document ถ้ามี เพราะอาจไม่มี element ที่ตรง select_first return Option<ElementRef> สุดท้าย เราใช้เมธอด Option::map ซึ่ง ให้เราทำงานกับ item ใน Option ถ้ามี และไม่ทำอะไรถ้าไม่มี (เรา ใช้ expression match ที่นี่ได้ด้วย แต่ map เป็น idiomatic มากกว่า) ใน body ของฟังก์ชันที่เราให้ map เราเรียก inner_html บน title เพื่อรับเนื้อหาของมัน ซึ่งเป็น String เมื่อทุกอย่าง พูดและทำ เรามี Option<String>

สังเกตว่า keyword await ของ Rust ไป หลัง expression ที่คุณกำลัง await ไม่ใช่ก่อน นั่นคือ มันเป็น keyword postfix นี่อาจต่างจาก ที่คุณคุ้นเคยถ้าคุณใช้ async ในภาษาอื่น แต่ใน Rust มันทำให้ chain ของเมธอดทำงานกับด้วยดีกว่ามาก ผลคือ เราเปลี่ยน body ของ page_title ให้ chain การเรียกฟังก์ชัน trpl::get และ text ด้วย await ระหว่างพวกมันได้ ดังที่แสดงใน Listing 17-2

Filename: src/main.rs
extern crate trpl; // required for mdbook test

use trpl::Html;

fn main() {
    // TODO: we'll add this next!
}

async fn page_title(url: &str) -> Option<String> {
    let response_text = trpl::get(url).await.text().await;
    Html::parse(&response_text)
        .select_first("title")
        .map(|title| title.inner_html())
}
Listing 17-2: Chain ด้วย keyword await

ด้วยนั้น เราเขียนฟังก์ชัน async แรกของเราสำเร็จแล้ว! ก่อนเราเพิ่ม โค้ดใน main เพื่อเรียกมัน มาพูดเพิ่มเล็กน้อยเกี่ยวกับสิ่งที่เรา เขียนและมันหมายความอะไร

เมื่อ Rust เห็น block ที่ทำเครื่องหมายด้วย keyword async มัน คอมไพล์มันเป็นประเภทข้อมูล unique และ anonymous ที่ implement trait Future เมื่อ Rust เห็น ฟังก์ชัน ที่ทำเครื่องหมายด้วย async มันคอมไพล์มันเป็นฟังก์ชันที่ไม่ใช่ async ที่ body ของมันคือ block async return type ของฟังก์ชัน async คือ type ของประเภทข้อมูล anonymous ที่ compiler สร้างสำหรับ block async นั้น

ดังนั้น การเขียน async fn เทียบเท่ากับการเขียนฟังก์ชันที่ return future ของ return type ต่อ compiler นิยามฟังก์ชันเช่น async fn page_title ใน Listing 17-1 ประมาณเทียบเท่ากับฟังก์ชัน ที่ไม่ใช่ async ที่นิยามแบบนี้:

#![allow(unused)]
fn main() {
extern crate trpl; // required for mdbook test
use std::future::Future;
use trpl::Html;

fn page_title(url: &str) -> impl Future<Output = Option<String>> {
    async move {
        let text = trpl::get(url).await.text().await;
        Html::parse(&text)
            .select_first("title")
            .map(|title| title.inner_html())
    }
}
}

มาเดินผ่านแต่ละส่วนของเวอร์ชันที่แปลง:

  • มันใช้ syntax impl Trait ที่เราพูดถึงในบทที่ 10 ในส่วน “Trait เป็น Parameter”
  • ค่าที่ return implement trait Future ที่มี associated type Output สังเกตว่า type Output คือ Option<String> ซึ่งเหมือน กับ return type ดั้งเดิมจากเวอร์ชัน async fn ของ page_title
  • โค้ดทั้งหมดที่เรียกใน body ของฟังก์ชันดั้งเดิมถูก wrap ใน block async move จำได้ว่า block เป็น expression block ทั้งหมดนี้คือ expression ที่ return จากฟังก์ชัน
  • block async นี้สร้างค่ากับ type Option<String> ดังที่อธิบาย ค่านั้นตรงกับ type Output ใน return type นี่เหมือนกับ block อื่น ที่คุณเห็น
  • body ฟังก์ชันใหม่เป็น block async move เพราะวิธีที่มันใช้ parameter url (เราจะพูดเพิ่มมากเกี่ยวกับ async เทียบกับ async move ภายหลังในบท)

ตอนนี้เราเรียก page_title ใน main ได้

Execute ฟังก์ชัน Async ด้วย Runtime

เพื่อเริ่ม เราจะรับ title สำหรับหน้าเดียว แสดงใน Listing 17-3 โชค ไม่ดี โค้ดนี้ยังไม่คอมไพล์

Filename: src/main.rs
extern crate trpl; // required for mdbook test

use trpl::Html;

async fn main() {
    let args: Vec<String> = std::env::args().collect();
    let url = &args[1];
    match page_title(url).await {
        Some(title) => println!("The title for {url} was {title}"),
        None => println!("{url} had no title"),
    }
}

async fn page_title(url: &str) -> Option<String> {
    let response_text = trpl::get(url).await.text().await;
    Html::parse(&response_text)
        .select_first("title")
        .map(|title| title.inner_html())
}
Listing 17-3: เรียกฟังก์ชัน page_title จาก main กับอาร์กิวเมนต์ที่ user ให้

เราตาม pattern เดียวกับที่เราใช้รับอาร์กิวเมนต์ command line ในส่วน “รับ Command Line Argument” ในบทที่ 12 จากนั้นเราส่งอาร์กิวเมนต์ URL ให้ page_title และ await ผล เพราะ ค่าที่ผลิตโดย future คือ Option<String> เราใช้ expression match เพื่อ print ข้อความต่างกันเพื่อรองรับว่าหน้ามี <title> ไหม

ที่เดียวที่เราใช้ keyword await ได้คือในฟังก์ชันหรือ block async และ Rust จะไม่ให้เราทำเครื่องหมายฟังก์ชันพิเศษ main เป็น async

error[E0752]: `main` function is not allowed to be `async`
 --> src/main.rs:6:1
  |
6 | async fn main() {
  | ^^^^^^^^^^^^^^^ `main` function is not allowed to be `async`

เหตุผลที่ main ทำเครื่องหมาย async ไม่ได้คือโค้ด async ต้องการ runtime — crate Rust ที่จัดการรายละเอียดของการ execute โค้ด asynchronous ฟังก์ชัน main ของโปรแกรม initialize runtime ได้ แต่มันไม่ใช่ runtime เอง (เราจะเห็นเพิ่มเกี่ยวกับทำไมนี่เป็นกรณี ในไม่ช้า) ทุกโปรแกรม Rust ที่ execute โค้ด async มีอย่างน้อยหนึ่งที่ ที่มันตั้ง runtime ที่ execute future

ภาษาส่วนใหญ่ที่สนับสนุน async bundle runtime แต่ Rust ไม่ แทน มี async runtime ต่างกันหลายตัวให้ใช้ แต่ละตัวทำ tradeoff ต่างกันเหมาะ สำหรับ use case ที่มันเป้าหมาย ตัวอย่างเช่น web server high-throughput กับหลาย CPU core และ RAM จำนวนมากมีความต้องการต่างมาก กับ microcontroller ที่มี core เดียว, RAM จำนวนน้อย และไม่มีความ สามารถ heap allocation crate ที่ให้ runtime เหล่านั้นยังมักจัดส่ง async version ของ functionality ทั่วไปเช่น I/O ไฟล์หรือ network

ที่นี่และตลอดที่เหลือของบทนี้ เราจะใช้ฟังก์ชัน block_on จาก crate trpl ซึ่งรับ future เป็นอาร์กิวเมนต์และ block เธรดปัจจุบันจนกว่า future นี้รันจนเสร็จ เบื้องหลัง การเรียก block_on ตั้ง runtime โดยใช้ crate tokio ที่ใช้รัน future ที่ส่งเข้า (พฤติกรรม block_on ของ crate trpl คล้ายกับฟังก์ชัน block_on ของ runtime crate อื่น) เมื่อ future เสร็จ block_on return ค่าใดที่ future สร้าง

เราส่ง future ที่ return โดย page_title ให้ block_on โดยตรง และเมื่อมันเสร็จ เรา match บน Option<String> ที่ได้ดังที่เราพยายาม ทำใน Listing 17-3 ได้ อย่างไรก็ตาม สำหรับตัวอย่างส่วนใหญ่ในบท (และ โค้ด async ส่วนใหญ่ในโลกจริง) เราจะทำมากกว่าเพียงการเรียกฟังก์ชัน async หนึ่งครั้ง ดังนั้นแทน เราจะส่ง block async และ await ผลของ การเรียก page_title ชัดเจน ดังใน Listing 17-4

Filename: src/main.rs
extern crate trpl; // required for mdbook test

use trpl::Html;

fn main() {
    let args: Vec<String> = std::env::args().collect();

    trpl::block_on(async {
        let url = &args[1];
        match page_title(url).await {
            Some(title) => println!("The title for {url} was {title}"),
            None => println!("{url} had no title"),
        }
    })
}

async fn page_title(url: &str) -> Option<String> {
    let response_text = trpl::get(url).await.text().await;
    Html::parse(&response_text)
        .select_first("title")
        .map(|title| title.inner_html())
}
Listing 17-4: Await block async ด้วย trpl::block_on

เมื่อเรารันโค้ดนี้ เราได้พฤติกรรมที่เราคาดหวังในตอนแรก:

$ cargo run -- "https://www.rust-lang.org"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
     Running `target/debug/async_await 'https://www.rust-lang.org'`
The title for https://www.rust-lang.org was
            Rust Programming Language

โอ — ในที่สุดเรามีโค้ด async ที่ทำงาน! แต่ก่อนเราเพิ่มโค้ดเพื่อแข่ง สองไซต์กัน มาหันความสนใจสั้น ๆ กลับไปยังวิธีที่ future ทำงาน

แต่ละ จุด await — นั่นคือ ทุกที่ที่โค้ดใช้ keyword await — แทนที่ที่การควบคุมถูกส่งกลับให้ runtime เพื่อทำให้นั้นทำงาน Rust ต้องตามว่า state ที่เกี่ยวข้องใน block async เพื่อให้ runtime เริ่ม งานอื่นและจากนั้นกลับมาเมื่อมันพร้อมที่จะลองก้าวหน้าอันแรกอีก นี่ คือ state machine ที่มองไม่เห็น เหมือนกับว่าคุณเขียน enum แบบนี้ เพื่อบันทึก state ปัจจุบันที่แต่ละจุด await:

#![allow(unused)]
fn main() {
extern crate trpl; // required for mdbook test

enum PageTitleFuture<'a> {
    Initial { url: &'a str },
    GetAwaitPoint { url: &'a str },
    TextAwaitPoint { response: trpl::Response },
}
}

การเขียนโค้ดเพื่อเปลี่ยนระหว่างแต่ละ state ด้วยมือจะน่าเบื่อและมี แนวโน้ม error อย่างไรก็ตาม โดยเฉพาะเมื่อคุณต้องเพิ่ม functionality และ state เพิ่มให้โค้ดภายหลัง โชคดี Rust compiler สร้างและจัดการ โครงสร้างข้อมูล state machine สำหรับโค้ด async อัตโนมัติ กฎ borrowing และ ownership ปกติรอบโครงสร้างข้อมูลทั้งหมดยังใช้ และโชค ดี compiler ยังจัดการการตรวจสอบเหล่านั้นให้เราและให้ข้อความ error ที่มีประโยชน์ เราจะทำงานผ่านบางส่วนของพวกนั้นภายหลังในบท

ในที่สุด อะไรบางอย่างต้อง execute state machine นี้ และอะไรบางอย่าง นั้นคือ runtime (นี่คือเหตุผลที่คุณอาจพบการกล่าวถึง executor เมื่อ ดูเข้า runtime — executor เป็นส่วนของ runtime ที่รับผิดชอบในการ execute โค้ด async)

ตอนนี้คุณเห็นได้ว่าทำไม compiler หยุดเราจากการทำ main เอง async function กลับใน Listing 17-3 ถ้า main เป็นฟังก์ชัน async อะไรบาง อย่างอื่นจะต้องจัดการ state machine สำหรับ future ใดที่ main return แต่ main คือจุดเริ่มของโปรแกรม! แทน เราเรียกฟังก์ชัน trpl::block_on ใน main เพื่อตั้ง runtime และรัน future ที่ return โดย block async จนกว่ามันเสร็จ

สังเกต — บาง runtime ให้ macro เพื่อคุณ สามารถ เขียนฟังก์ชัน main async ได้ macro เหล่านั้นเขียน async fn main() { ... } ใหม่เป็น fn main ปกติ ซึ่งทำสิ่งเดียวกับที่เราทำด้วยมือใน Listing 17-4 — เรียกฟังก์ชันที่รัน future จนเสร็จในแบบที่ trpl::block_on ทำ

ตอนนี้มาใส่ชิ้นส่วนเหล่านี้รวมกันและเห็นว่าเราเขียนโค้ด concurrent ยังไงได้

แข่งสอง URL แบบ Concurrent

ใน Listing 17-5 เราเรียก page_title กับสอง URL ต่างกันที่ส่งเข้า จาก command line และแข่งพวกมันโดยเลือก future ใดที่เสร็จก่อน

Filename: src/main.rs
extern crate trpl; // required for mdbook test

use trpl::{Either, Html};

fn main() {
    let args: Vec<String> = std::env::args().collect();

    trpl::block_on(async {
        let title_fut_1 = page_title(&args[1]);
        let title_fut_2 = page_title(&args[2]);

        let (url, maybe_title) =
            match trpl::select(title_fut_1, title_fut_2).await {
                Either::Left(left) => left,
                Either::Right(right) => right,
            };

        println!("{url} returned first");
        match maybe_title {
            Some(title) => println!("Its page title was: '{title}'"),
            None => println!("It had no title."),
        }
    })
}

async fn page_title(url: &str) -> (&str, Option<String>) {
    let response_text = trpl::get(url).await.text().await;
    let title = Html::parse(&response_text)
        .select_first("title")
        .map(|title| title.inner_html());
    (url, title)
}
Listing 17-5: เรียก page_title สำหรับสอง URL เพื่อดูว่าอันไหน return ก่อน

เราเริ่มโดยเรียก page_title สำหรับแต่ละของ URL ที่ user ให้ เรา บันทึก future ที่ได้เป็น title_fut_1 และ title_fut_2 จำได้ พวก มันไม่ทำอะไรยัง เพราะ future เป็น lazy และเรายังไม่ได้ await พวก มัน จากนั้นเราส่ง future ให้ trpl::select ซึ่ง return ค่าเพื่อ ระบุว่าอันไหนของ future ที่ส่งให้มันเสร็จก่อน

สังเกต — ใต้ฝ่ามือ trpl::select ถูก build บนฟังก์ชัน select ทั่วไปกว่าที่นิยามใน crate futures ฟังก์ชัน select ของ crate futures ทำสิ่งหลายอย่างที่ฟังก์ชัน trpl::select ทำไม่ได้ แต่ มันยังมีความซับซ้อนเพิ่มที่เราข้ามได้ตอนนี้

future ใดก็ “ชนะ” อย่างถูกต้องตามกฎหมายได้ ดังนั้นมันไม่สมเหตุสมผล ที่จะ return Result แทน trpl::select return type ที่เรายังไม่ เห็นก่อน trpl::Either type Either คล้ายกับ Result หน่อยตรงที่ มันมีสองกรณี ต่างจาก Result อย่างไรก็ตาม ไม่มีแนวคิดของความสำเร็จ หรือ failure ที่ฝังใน Either แทน มันใช้ Left และ Right เพื่อ ระบุ “อันหนึ่งหรืออีกอัน”:

#![allow(unused)]
fn main() {
enum Either<A, B> {
    Left(A),
    Right(B),
}
}

ฟังก์ชัน select return Left กับ output ของ future นั้นถ้า อาร์กิวเมนต์แรกชนะ และ Right กับ output ของอาร์กิวเมนต์ future ที่สองถ้า อันนั้น ชนะ นี่ตรงลำดับที่อาร์กิวเมนต์ปรากฏเมื่อเรียก ฟังก์ชัน — อาร์กิวเมนต์แรกอยู่ทางซ้ายของอาร์กิวเมนต์ที่สอง

เรายังอัพเดท page_title ให้ return URL เดียวกันที่ส่งเข้า ด้วย วิธีนั้น ถ้าหน้าที่ return ก่อนไม่มี <title> ที่เรา resolve ได้ เรายัง print ข้อความที่มีความหมายได้ ด้วยข้อมูลนั้นใช้ได้ เราปิด โดยอัพเดท output println! ของเราเพื่อระบุทั้งว่า URL ไหนเสร็จก่อน และอะไร ถ้ามี <title> สำหรับหน้าเว็บที่ URL นั้น

คุณได้ build web scraper ที่ทำงานเล็กแล้ว! เลือกสอง URL และรัน เครื่องมือ command line คุณอาจค้นพบว่าบางไซต์เร็วกว่าอื่นอย่าง สม่ำเสมอ ในขณะที่ในบางกรณีไซต์ที่เร็วกว่าต่างกันจากการรันถึงการรัน สำคัญกว่า คุณเรียนพื้นฐานของการทำงานกับ future แล้ว ดังนั้นตอนนี้เรา ขุดลึกขึ้นในสิ่งที่เราทำกับ async ได้