ফিউচার এবং Async সিনট্যাক্স (Futures and the Async Syntax)

Rust-এ অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং-এর মূল উপাদানগুলি হল ফিউচার এবং Rust-এর async এবং await কীওয়ার্ড।

একটি ফিউচার হল এমন একটি মান যা এখনই প্রস্তুত নাও হতে পারে কিন্তু ভবিষ্যতে কোনো এক সময়ে প্রস্তুত হবে। (এই একই ধারণাটি অনেক ভাষায় দেখা যায়, কখনও কখনও অন্য নামে যেমন টাস্ক বা প্রমিজ)। Rust একটি Future trait সরবরাহ করে একটি বিল্ডিং ব্লক হিসাবে যাতে বিভিন্ন অ্যাসিঙ্ক্রোনাস অপারেশনগুলি বিভিন্ন ডেটা স্ট্রাকচার কিন্তু একটি সাধারণ ইন্টারফেসের সাথে ইমপ্লিমেন্ট করা যায়। Rust-এ, ফিউচার হল এমন টাইপ যা Future trait ইমপ্লিমেন্ট করে। প্রতিটি ফিউচার তার নিজস্ব তথ্য রাখে যে কতটা অগ্রগতি হয়েছে এবং "প্রস্তুত" মানে কী।

আপনি ব্লক এবং ফাংশনগুলিতে async কীওয়ার্ড প্রয়োগ করতে পারেন এটা বোঝাতে যে সেগুলিকে বাধা দেওয়া এবং পুনরায় শুরু করা যেতে পারে। একটি async ব্লক বা async ফাংশনের মধ্যে, আপনি await কীওয়ার্ড ব্যবহার করতে পারেন একটি ফিউচারের জন্য অপেক্ষা করার জন্য (অর্থাৎ, এটি প্রস্তুত হওয়ার জন্য অপেক্ষা করা)। একটি async ব্লক বা ফাংশনের মধ্যে আপনি যেখানেই একটি ফিউচারের জন্য অপেক্ষা করেন সেটি হল সেই async ব্লক বা ফাংশনের বিরতি এবং পুনরায় শুরু করার একটি সম্ভাব্য স্থান। একটি ফিউচারের সাথে তার মান এখনও উপলব্ধ কিনা তা দেখার জন্য পরীক্ষা করার প্রক্রিয়াটিকে পোলিং বলা হয়।

অন্যান্য কিছু ভাষা, যেমন C# এবং JavaScript-ও অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংয়ের জন্য async এবং await কীওয়ার্ড ব্যবহার করে। আপনি যদি সেই ভাষাগুলির সাথে পরিচিত হন তবে আপনি Rust কীভাবে কাজ করে, সিনট্যাক্স কীভাবে পরিচালনা করে সহ বেশ কয়েকটি উল্লেখযোগ্য পার্থক্য লক্ষ্য করতে পারেন। এর যথেষ্ট কারণ আছে, যেমনটি আমরা দেখতে পাব!

অ্যাসিঙ্ক্রোনাস Rust লেখার সময়, আমরা বেশিরভাগ সময় async এবং await কীওয়ার্ড ব্যবহার করি। Rust সেগুলিকে Future trait ব্যবহার করে সমতুল্য কোডে কম্পাইল করে, অনেকটা যেভাবে এটি for লুপগুলিকে Iterator trait ব্যবহার করে সমতুল্য কোডে কম্পাইল করে। যেহেতু Rust Future trait সরবরাহ করে, তাই প্রয়োজনে আপনি আপনার নিজের ডেটা টাইপের জন্যও এটি ইমপ্লিমেন্ট করতে পারেন। আমরা এই চ্যাপ্টার জুড়ে যে ফাংশনগুলি দেখব তার অনেকগুলি Future-এর নিজস্ব ইমপ্লিমেন্টেশন সহ টাইপ রিটার্ন করে। আমরা চ্যাপ্টারের শেষে trait-এর সংজ্ঞায় ফিরে যাব এবং এটি কীভাবে কাজ করে সে সম্পর্কে আরও গভীরে আলোচনা করব, তবে এটি আমাদের এগিয়ে যাওয়ার জন্য যথেষ্ট বিশদ।

এই সমস্ত কিছু বিমূর্ত মনে হতে পারে, তাই আসুন আমাদের প্রথম অ্যাসিঙ্ক্রোনাস প্রোগ্রামটি লিখি: একটি ছোট ওয়েব স্ক্র্যাপার। আমরা কমান্ড লাইন থেকে দুটি URL পাস করব, উভয়কেই কনকারেন্টলি ফেচ করব এবং যেটি প্রথমে শেষ হবে তার ফলাফল রিটার্ন করব। এই উদাহরণে বেশ কিছুটা নতুন সিনট্যাক্স থাকবে, তবে চিন্তা করবেন না—আমরা যাওয়ার পথে আপনার যা জানা দরকার তা ব্যাখ্যা করব।

আমাদের প্রথম অ্যাসিঙ্ক্রোনাস প্রোগ্রাম (Our First Async Program)

এই চ্যাপ্টারের ফোকাস ইকোসিস্টেমের বিভিন্ন অংশ নিয়ে কাজ করার পরিবর্তে অ্যাসিঙ্ক্রোনাস শেখার উপর রাখার জন্য, আমরা trpl ক্রেট তৈরি করেছি (trpl হল “The Rust Programming Language”-এর সংক্ষিপ্ত)। এটি আপনার প্রয়োজনীয় সমস্ত টাইপ, trait এবং ফাংশনগুলিকে পুনরায় এক্সপোর্ট করে, প্রাথমিকভাবে futures এবং tokio ক্রেটগুলি থেকে। futures ক্রেটটি অ্যাসিঙ্ক্রোনাস কোডের জন্য Rust এক্সপেরিমেন্টেশনের একটি অফিশিয়াল হোম, এবং এটি আসলে যেখানে Future trait মূলত ডিজাইন করা হয়েছিল। Tokio হল আজকের Rust-এ সর্বাধিক ব্যবহৃত অ্যাসিঙ্ক্রোনাস রানটাইম, বিশেষ করে ওয়েব অ্যাপ্লিকেশনগুলির জন্য। অন্যান্য দুর্দান্ত রানটাইম রয়েছে এবং সেগুলি আপনার উদ্দেশ্যের জন্য আরও উপযুক্ত হতে পারে। আমরা trpl-এর জন্য হুডের নিচে tokio ক্রেট ব্যবহার করি কারণ এটি ভালভাবে পরীক্ষিত এবং ব্যাপকভাবে ব্যবহৃত।

কিছু ক্ষেত্রে, trpl এই চ্যাপ্টারের সাথে প্রাসঙ্গিক বিশদগুলিতে আপনাকে ফোকাস রাখতে মূল API-গুলিকে রিনেম বা র‍্যাপ করে। আপনি যদি ক্রেটটি কী করে তা বুঝতে চান তবে আমরা আপনাকে এর সোর্স কোড দেখার জন্য উৎসাহিত করি। আপনি দেখতে পারবেন প্রতিটি রি-এক্সপোর্ট কোন ক্রেট থেকে আসে এবং আমরা ক্রেটটি কী করে তা ব্যাখ্যা করে বিস্তৃত মন্তব্য রেখেছি।

hello-async নামে একটি নতুন বাইনারি প্রোজেক্ট তৈরি করুন এবং trpl ক্রেটটিকে একটি ডিপেন্ডেন্সি হিসাবে যুক্ত করুন:

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

এখন আমরা আমাদের প্রথম অ্যাসিঙ্ক্রোনাস প্রোগ্রামটি লিখতে trpl দ্বারা প্রদত্ত বিভিন্ন অংশ ব্যবহার করতে পারি। আমরা একটি ছোট কমান্ড লাইন টুল তৈরি করব যা দুটি ওয়েব পেজ ফেচ করে, প্রতিটি থেকে <title> এলিমেন্ট টেনে আনে এবং যে পেজটি প্রথমে সেই পুরো প্রক্রিয়াটি শেষ করে তার টাইটেল প্রিন্ট করে।

page_title ফাংশন সংজ্ঞায়িত করা (Defining the page_title Function)

আসুন একটি ফাংশন লিখে শুরু করি যা একটি প্যারামিটার হিসাবে একটি পেজ URL নেয়, এটিতে একটি রিকোয়েস্ট করে এবং টাইটেল এলিমেন্টের টেক্সট রিটার্ন করে (লিস্টিং 17-1 দেখুন)।

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_element| title_element.inner_html())
}

প্রথমে, আমরা page_title নামে একটি ফাংশন সংজ্ঞায়িত করি এবং এটিকে async কীওয়ার্ড দিয়ে চিহ্নিত করি। তারপর আমরা trpl::get ফাংশন ব্যবহার করি যে URL পাস করা হয়েছে সেটি ফেচ করতে এবং রেসপন্সের জন্য অপেক্ষা করতে await কীওয়ার্ড যুক্ত করি। রেসপন্সের টেক্সট পেতে, আমরা এর text মেথড কল করি এবং আবারও await কীওয়ার্ড দিয়ে এটির জন্য অপেক্ষা করি। এই দুটি ধাপই অ্যাসিঙ্ক্রোনাস। get ফাংশনের জন্য, আমাদের সার্ভারের রেসপন্সের প্রথম অংশটি ফেরত পাঠানোর জন্য অপেক্ষা করতে হবে, যার মধ্যে HTTP হেডার, কুকি ইত্যাদি অন্তর্ভুক্ত থাকবে এবং রেসপন্স বডি থেকে আলাদাভাবে ডেলিভার করা যেতে পারে। বিশেষ করে যদি বডি খুব বড় হয়, তবে এটির সমস্তটা আসতে কিছুটা সময় লাগতে পারে। যেহেতু আমাদের রেসপন্সের সম্পূর্ণতা আসার জন্য অপেক্ষা করতে হবে, তাই text মেথডটিও অ্যাসিঙ্ক্রোনাস।

আমাদের এই দুটি ফিউচারের জন্যই স্পষ্টভাবে অপেক্ষা করতে হবে, কারণ Rust-এ ফিউচারগুলি লেজি: await কীওয়ার্ড দিয়ে আপনি তাদের না বলা পর্যন্ত তারা কিছুই করে না। (আসলে, আপনি যদি একটি ফিউচার ব্যবহার না করেন তবে Rust একটি কম্পাইলার সতর্কতা দেখাবে।) এটি আপনাকে Chapter 13-এর Processing a Series of Items With Iterators বিভাগের ইটারেটর নিয়ে আলোচনার কথা মনে করিয়ে দিতে পারে। ইটারেটরগুলি যতক্ষণ না আপনি তাদের next মেথড কল করেন ততক্ষণ কিছুই করে না—সরাসরি বা for লুপ বা map-এর মতো মেথড ব্যবহার করে যা হুডের নিচে next ব্যবহার করে। একইভাবে, ফিউচারগুলি আপনি তাদের স্পষ্টভাবে করতে না বললে কিছুই করে না। এই লেজিনেস Rust-কে অ্যাসিঙ্ক্রোনাস কোড চালানো এড়াতে দেয় যতক্ষণ না এটির আসলেই প্রয়োজন হয়।

Note: এটি Creating a New Thread with spawn-এ thread::spawn ব্যবহার করার সময় আমরা আগের চ্যাপ্টারে যে আচরণ দেখেছি তার থেকে আলাদা, যেখানে আমরা অন্য থ্রেডে যে ক্লোজারটি পাস করেছি সেটি অবিলম্বে চলতে শুরু করে। এটি অন্যান্য অনেক ভাষা যেভাবে অ্যাসিঙ্ক্রোনাস অ্যাপ্রোচ করে তার থেকেও আলাদা। কিন্তু Rust-এর জন্য তার পারফরম্যান্স গ্যারান্টি সরবরাহ করতে সক্ষম হওয়া গুরুত্বপূর্ণ, যেমনটি ইটারেটরের ক্ষেত্রে।

একবার আমাদের কাছে response_text থাকলে, আমরা এটিকে Html::parse ব্যবহার করে Html টাইপের একটি ইনস্ট্যান্সে পার্স করতে পারি। একটি raw string-এর পরিবর্তে, আমাদের কাছে এখন একটি ডেটা টাইপ রয়েছে যা আমরা HTML-এর সাথে একটি সমৃদ্ধ ডেটা স্ট্রাকচার হিসাবে কাজ করতে ব্যবহার করতে পারি। বিশেষ করে, আমরা একটি প্রদত্ত CSS নির্বাচকের প্রথম ইনস্ট্যান্স খুঁজে বের করতে select_first মেথড ব্যবহার করতে পারি। স্ট্রিং "title" পাস করে, আমরা ডকুমেন্টের প্রথম <title> এলিমেন্টটি পাব, যদি থাকে। যেহেতু কোনও ম্যাচিং এলিমেন্ট নাও থাকতে পারে, তাই select_first একটি Option<ElementRef> রিটার্ন করে। অবশেষে, আমরা Option::map মেথড ব্যবহার করি, যা আমাদের Option-এর মধ্যে থাকা আইটেমটির সাথে কাজ করতে দেয় যদি এটি উপস্থিত থাকে এবং যদি না থাকে তবে কিছুই করে না। (আমরা এখানে একটি match এক্সপ্রেশনও ব্যবহার করতে পারতাম, কিন্তু map আরও বেশি প্রচলিত।) আমরা map-কে যে ফাংশন সরবরাহ করি তার বডিতে, আমরা title_element-এ inner_html কল করি তার কনটেন্ট পেতে, যা একটি String। সবশেষে, আমাদের কাছে একটি Option<String> থাকে।

লক্ষ্য করুন যে Rust-এর await কীওয়ার্ডটি আপনি যে এক্সপ্রেশনের জন্য অপেক্ষা করছেন তার পরে বসে, আগে নয়। অর্থাৎ, এটি একটি পোস্টফিক্স কীওয়ার্ড। আপনি যদি অন্যান্য ভাষায় async ব্যবহার করে থাকেন তবে এটি আপনার অভ্যস্ত হওয়ার থেকে আলাদা হতে পারে, তবে Rust-এ এটি মেথডের চেইনগুলির সাথে কাজ করা অনেক সুন্দর করে তোলে। ফলস্বরূপ, আমরা page_url_for-এর বডি পরিবর্তন করতে পারি trpl::get এবং text ফাংশন কলগুলিকে একসাথে চেইন করতে এবং তাদের মধ্যে await ব্যবহার করতে, যেমনটি Listing 17-2-এ দেখানো হয়েছে।

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_element| title_element.inner_html())
}

এর সাথে, আমরা সফলভাবে আমাদের প্রথম অ্যাসিঙ্ক্রোনাস ফাংশনটি লিখেছি! main-এ এটি কল করার জন্য কিছু কোড যুক্ত করার আগে, আসুন আমরা যা লিখেছি এবং এর অর্থ কী সে সম্পর্কে আরও একটু কথা বলি।

যখন Rust async কীওয়ার্ড দিয়ে চিহ্নিত একটি ব্লক দেখে, তখন এটি এটিকে একটি অনন্য, বেনামী ডেটা টাইপে কম্পাইল করে যা Future trait ইমপ্লিমেন্ট করে। যখন Rust async দিয়ে চিহ্নিত একটি ফাংশন দেখে, তখন এটি এটিকে একটি non-async ফাংশনে কম্পাইল করে যার বডি একটি অ্যাসিঙ্ক্রোনাস ব্লক। একটি অ্যাসিঙ্ক্রোনাস ফাংশনের রিটার্ন টাইপ হল কম্পাইলার সেই অ্যাসিঙ্ক্রোনাস ব্লকের জন্য যে বেনামী ডেটা টাইপ তৈরি করে তার টাইপ।

সুতরাং, async fn লেখা একটি ফাংশন লেখার সমতুল্য যা রিটার্ন টাইপের একটি ফিউচার রিটার্ন করে। কম্পাইলারের কাছে, Listing 17-1-এর async fn page_title-এর মতো একটি ফাংশন সংজ্ঞা এইরকমভাবে সংজ্ঞায়িত একটি non-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())
    }
}
}

আসুন রূপান্তরিত সংস্করণের প্রতিটি অংশের মধ্য দিয়ে যাই:

  • এটি impl Trait সিনট্যাক্স ব্যবহার করে যা আমরা Chapter 10-এর “Traits as Parameters” বিভাগে আলোচনা করেছি।
  • রিটার্ন করা trait হল একটি Future যার সাথে একটি Output টাইপ যুক্ত। লক্ষ্য করুন যে Output টাইপটি হল Option<String>, যা page_title-এর async fn সংস্করণের মূল রিটার্ন টাইপের মতোই।
  • মূল ফাংশনের বডিতে কল করা সমস্ত কোড একটি async move ব্লকের মধ্যে র‍্যাপ করা হয়েছে। মনে রাখবেন যে ব্লকগুলি হল এক্সপ্রেশন। এই সম্পূর্ণ ব্লকটি হল ফাংশন থেকে রিটার্ন করা এক্সপ্রেশন।
  • এই অ্যাসিঙ্ক্রোনাস ব্লকটি Option<String> টাইপের একটি মান তৈরি করে, যেমনটি বর্ণনা করা হয়েছে। এই মানটি রিটার্ন টাইপের Output টাইপের সাথে মেলে। এটি আপনার দেখা অন্যান্য ব্লকের মতোই।
  • নতুন ফাংশন বডিটি একটি async move ব্লক কারণ এটি url প্যারামিটারটি কীভাবে ব্যবহার করে। (আমরা এই চ্যাপ্টারে পরে async বনাম async move সম্পর্কে আরও অনেক কিছু আলোচনা করব।)

এখন আমরা main-এ page_title কল করতে পারি।

একটি একক পেজের টাইটেল নির্ধারণ করা (Determining a Single Page’s Title)

শুরু করার জন্য, আমরা কেবল একটি একক পেজের জন্য টাইটেল পাব। Listing 17-3-এ, আমরা Accepting Command Line Arguments বিভাগে কমান্ড লাইনের আর্গুমেন্ট পেতে Chapter 12-এ ব্যবহৃত একই প্যাটার্ন অনুসরণ করি। তারপর আমরা প্রথম URL page_title-এ পাস করি এবং ফলাফলের জন্য অপেক্ষা করি। যেহেতু ফিউচার দ্বারা উৎপাদিত মানটি একটি Option<String>, তাই পেজটিতে একটি <title> আছে কিনা তা বিবেচনা করার জন্য আমরা বিভিন্ন মেসেজ প্রিন্ট করতে একটি match এক্সপ্রেশন ব্যবহার করি।

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_element| title_element.inner_html())
}

দুর্ভাগ্যবশত, এই কোডটি কম্পাইল হয় না। আমরা await কীওয়ার্ডটি শুধুমাত্র অ্যাসিঙ্ক্রোনাস ফাংশন বা ব্লকে ব্যবহার করতে পারি এবং 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 হিসাবে চিহ্নিত করা যায় না তার কারণ হল অ্যাসিঙ্ক্রোনাস কোডের একটি রানটাইম প্রয়োজন: একটি Rust ক্রেট যা অ্যাসিঙ্ক্রোনাস কোড এক্সিকিউট করার বিশদ পরিচালনা করে। একটি প্রোগ্রামের main ফাংশন একটি রানটাইম ইনিশিয়ালাইজ করতে পারে, কিন্তু এটি নিজে একটি রানটাইম নয়। (আমরা একটু পরেই দেখব কেন এটি এইরকম।) প্রতিটি Rust প্রোগ্রাম যা অ্যাসিঙ্ক্রোনাস কোড এক্সিকিউট করে সেখানে অন্তত একটি স্থান রয়েছে যেখানে এটি একটি রানটাইম সেট আপ করে এবং ফিউচারগুলি এক্সিকিউট করে।

বেশিরভাগ ভাষা যারা অ্যাসিঙ্ক্রোনাস সমর্থন করে তারা একটি রানটাইম বান্ডেল করে, কিন্তু Rust তা করে না। পরিবর্তে, অনেকগুলি বিভিন্ন অ্যাসিঙ্ক্রোনাস রানটাইম উপলব্ধ রয়েছে, যার প্রতিটি যে ব্যবহারের ক্ষেত্রে টার্গেট করে তার জন্য উপযুক্ত বিভিন্ন ট্রেডঅফ তৈরি করে। উদাহরণস্বরূপ, অনেকগুলি CPU কোর এবং প্রচুর পরিমাণে RAM সহ একটি high-throughput ওয়েব সার্ভারের একটি single কোর, অল্প পরিমাণে RAM এবং কোনও হিপ অ্যালোকেশন ক্ষমতা নেই এমন একটি মাইক্রোকন্ট্রোলারের চেয়ে খুব আলাদা চাহিদা রয়েছে। সেই রানটাইমগুলি সরবরাহ করা ক্রেটগুলি প্রায়শই ফাইল বা নেটওয়ার্ক I/O-এর মতো সাধারণ কার্যকারিতার অ্যাসিঙ্ক্রোনাস সংস্করণও সরবরাহ করে।

এখানে, এবং এই চ্যাপ্টারের বাকি অংশ জুড়ে, আমরা trpl ক্রেট থেকে run ফাংশনটি ব্যবহার করব, যা একটি আর্গুমেন্ট হিসাবে একটি ফিউচার নেয় এবং এটি সম্পূর্ণ হওয়া পর্যন্ত চালায়। পর্দার আড়ালে, run কল করা একটি রানটাইম সেট আপ করে যা পাস করা ফিউচারটি চালানোর জন্য ব্যবহৃত হয়। ফিউচারটি সম্পূর্ণ হয়ে গেলে, run ফিউচারটি যে মান তৈরি করেছে তা রিটার্ন করে।

আমরা page_title দ্বারা রিটার্ন করা ফিউচারটি সরাসরি run-এ পাস করতে পারি এবং এটি সম্পূর্ণ হয়ে গেলে, আমরা Listing 17-3-এ করার চেষ্টা করার মতো ফলাফলের Option<String>-এ ম্যাচ করতে পারি। যাইহোক, চ্যাপ্টারের বেশিরভাগ উদাহরণের জন্য (এবং বাস্তব বিশ্বের বেশিরভাগ অ্যাসিঙ্ক্রোনাস কোডের জন্য), আমরা শুধুমাত্র একটি অ্যাসিঙ্ক্রোনাস ফাংশন কলের চেয়ে আরও বেশি কিছু করব, তাই পরিবর্তে আমরা একটি async ব্লক পাস করব এবং Listing 17-4-এর মতো page_title কলের ফলাফলের জন্য স্পষ্টভাবে অপেক্ষা করব।

extern crate trpl; // required for mdbook test

use trpl::Html;

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

    trpl::run(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_element| title_element.inner_html())
}

যখন আমরা এই কোডটি চালাই, তখন আমরা প্রাথমিকভাবে প্রত্যাশিত আচরণটি পাই:

$ 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

উফ—অবশেষে আমাদের কাছে কিছু কার্যকরী অ্যাসিঙ্ক্রোনাস কোড আছে! কিন্তু দুটি সাইট একে অপরের বিরুদ্ধে রেস করানোর কোড যুক্ত করার আগে, আসুন সংক্ষেপে ফিউচারগুলি কীভাবে কাজ করে সেদিকে আমাদের মনোযোগ ফিরিয়ে দিই।

প্রতিটি অ্যাওয়েট পয়েন্ট—অর্থাৎ, কোডটি যেখানেই await কীওয়ার্ড ব্যবহার করে—এমন একটি স্থানকে উপস্থাপন করে যেখানে কন্ট্রোল রানটাইমে ফিরে যায়। এটি কাজ করার জন্য, Rust-কে অ্যাসিঙ্ক্রোনাস ব্লকের সাথে জড়িত স্টেট ট্র্যাক রাখতে হবে যাতে রানটাইম অন্য কিছু কাজ শুরু করতে পারে এবং তারপর প্রথমটিকে আবার অগ্রসর করার চেষ্টা করার জন্য প্রস্তুত হলে ফিরে আসতে পারে। এটি একটি অদৃশ্য স্টেট মেশিন, যেন আপনি প্রতিটি অ্যাওয়েট পয়েন্টে বর্তমান স্টেট সংরক্ষণ করার জন্য এইরকম একটি enum লিখেছেন:

#![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 },
}
}

প্রতিটি স্টেটের মধ্যে ট্রানজিশন করার জন্য হাতে কোড লেখা ক্লান্তিকর এবং ত্রুটি-প্রবণ হবে, বিশেষ করে যখন আপনাকে পরে কোডে আরও কার্যকারিতা এবং আরও স্টেট যুক্ত করতে হবে। সৌভাগ্যবশত, Rust কম্পাইলার অ্যাসিঙ্ক্রোনাস কোডের জন্য স্টেট মেশিন ডেটা স্ট্রাকচারগুলি স্বয়ংক্রিয়ভাবে তৈরি এবং পরিচালনা করে। ডেটা স্ট্রাকচারের চারপাশে স্বাভাবিক borrowing এবং ownership-এর নিয়মগুলি এখনও প্রযোজ্য, এবং আনন্দের বিষয়, কম্পাইলার আমাদের জন্য সেগুলিও পরীক্ষা করে এবং দরকারী error মেসেজ সরবরাহ করে। আমরা চ্যাপ্টারে পরে সেগুলির কয়েকটির মধ্য দিয়ে কাজ করব।

অবশেষে, কিছুকে এই স্টেট মেশিনটি এক্সিকিউট করতে হবে এবং সেই কিছু হল একটি রানটাইম। (এই কারণেই আপনি রানটাইমগুলি দেখার সময় এক্সিকিউটর-এর রেফারেন্স দেখতে পারেন: একজন এক্সিকিউটর হল একটি রানটাইমের অংশ যা অ্যাসিঙ্ক্রোনাস কোড এক্সিকিউট করার জন্য দায়ী।)

এখন আপনি দেখতে পাচ্ছেন কেন কম্পাইলার আমাদের Listing 17-3-এ main কেই একটি অ্যাসিঙ্ক্রোনাস ফাংশন করতে দেয়নি। যদি main একটি অ্যাসিঙ্ক্রোনাস ফাংশন হত, তাহলে অন্য কিছুকে main যে ফিউচার রিটার্ন করেছে তার স্টেট মেশিন পরিচালনা করতে হত, কিন্তু main হল প্রোগ্রামের শুরুর পয়েন্ট! পরিবর্তে, আমরা একটি রানটাইম সেট আপ করতে এবং async ব্লক দ্বারা রিটার্ন করা ফিউচারটি শেষ না হওয়া পর্যন্ত চালানোর জন্য main-এ trpl::run ফাংশনটি কল করেছি।

Note: কিছু রানটাইম ম্যাক্রো সরবরাহ করে যাতে আপনি একটি অ্যাসিঙ্ক্রোনাস main ফাংশন লিখতে পারেন। সেই ম্যাক্রোগুলি async fn main() { ... } কে একটি সাধারণ fn main-এ পুনর্লিখন করে, যা Listing 17-5-এ আমরা হাতে যা করেছি তাই করে: একটি ফাংশন কল করে যা trpl::run-এর মতো ফিউচারটি সম্পূর্ণ হওয়া পর্যন্ত চালায়।

এখন আসুন এই টুকরোগুলি একসাথে রাখি এবং দেখি কীভাবে আমরা কনকারেন্ট কোড লিখতে পারি।

আমাদের দুটি URL একে অপরের বিরুদ্ধে রেস করানো (Racing Our Two URLs Against Each Other)

Listing 17-5-এ, আমরা কমান্ড লাইন থেকে পাস করা দুটি ভিন্ন URL সহ page_title কল করি এবং তাদের রেস করাই।

extern crate trpl; // required for mdbook test

use trpl::{Either, Html};

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

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

        let (url, maybe_title) =
            match trpl::race(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 is: '{title}'"),
            None => println!("Its title could not be parsed."),
        }
    })
}

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

আমরা ব্যবহারকারী-সরবরাহকৃত URL-গুলির প্রত্যেকটির জন্য page_title কল করে শুরু করি। আমরা ফলাফলের ফিউচারগুলিকে title_fut_1 এবং title_fut_2 হিসাবে সংরক্ষণ করি। মনে রাখবেন, এগুলি এখনও কিছুই করে না, কারণ ফিউচারগুলি অলস এবং আমরা এখনও তাদের জন্য অপেক্ষা করিনি। তারপর আমরা ফিউচারগুলিকে trpl::race-এ পাস করি, যা এটিতে পাস করা ফিউচারগুলির মধ্যে কোনটি প্রথমে শেষ হয় তা নির্দেশ করার জন্য একটি মান রিটার্ন করে।

Note: হুডের নিচে, race একটি আরও সাধারণ ফাংশন, select-এর উপর নির্মিত, যা আপনি বাস্তব-বিশ্বের Rust কোডে আরও ঘন ঘন দেখতে পাবেন। একটি select ফাংশন এমন অনেক কিছু করতে পারে যা trpl::race ফাংশন পারে না, তবে এটির কিছু অতিরিক্ত জটিলতাও রয়েছে যা আমরা আপাতত এড়িয়ে যেতে পারি।

যেকোনো ফিউচার বৈধভাবে “জিততে” পারে, তাই একটি Result রিটার্ন করা অর্থবোধক নয়। পরিবর্তে, race এমন একটি টাইপ রিটার্ন করে যা আমরা আগে দেখিনি, trpl::EitherEither টাইপটি কিছুটা Result-এর মতো যে এটির দুটি কেস রয়েছে। Result-এর মতো, যদিও, Either-এ সাফল্য বা ব্যর্থতার কোনও ধারণা নেই। পরিবর্তে, এটি "একটি বা অন্যটি" বোঝাতে Left এবং Right ব্যবহার করে:

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

race ফাংশনটি প্রথম আর্গুমেন্ট জিতলে সেই ফিউচারের আউটপুট সহ Left এবং দ্বিতীয় ফিউচার আর্গুমেন্টের আউটপুট সহ Right রিটার্ন করে যদি সেটি জেতে। এটি ফাংশনটি কল করার সময় আর্গুমেন্টগুলি যে ক্রমে প্রদর্শিত হয় তার সাথে মেলে: প্রথম আর্গুমেন্টটি দ্বিতীয় আর্গুমেন্টের বাম দিকে।

আমরা page_title আপডেটও করি যাতে পাস করা একই URL রিটার্ন করা হয়। এইভাবে, যদি যে পেজটি প্রথমে রিটার্ন করে তাতে একটি <title> সমাধান করার মতো না থাকে, তাহলেও আমরা একটি অর্থপূর্ণ মেসেজ প্রিন্ট করতে পারি। সেই তথ্য উপলব্ধ থাকায়, আমরা আমাদের println! আউটপুট আপডেট করে শেষ করি যাতে কোন URLটি প্রথমে শেষ হয়েছে এবং সেই URL-এর ওয়েব পেজের জন্য <title> কী, যদি থাকে, তা নির্দেশ করা যায়।

আপনি এখন একটি ছোট কার্যকরী ওয়েব স্ক্র্যাপার তৈরি করেছেন! কয়েকটি URL বেছে নিন এবং কমান্ড লাইন টুলটি চালান। আপনি আবিষ্কার করতে পারেন যে কিছু সাইট ধারাবাহিকভাবে অন্যদের চেয়ে দ্রুত, আবার অন্য ক্ষেত্রে দ্রুত সাইটটি রান থেকে রানে পরিবর্তিত হয়। আরও গুরুত্বপূর্ণ, আপনি ফিউচারগুলির সাথে কাজ করার বেসিকগুলি শিখেছেন, তাই এখন আমরা অ্যাসিঙ্ক্রোনাস দিয়ে আমরা কী করতে পারি তার গভীরে ডুব দিতে পারি।