use কীওয়ার্ড দিয়ে পাথগুলোকে স্কোপে আনা (Bringing Paths into Scope with the use Keyword)

ফাংশন কল করার জন্য পাথগুলো লিখতে থাকা অসুবিধাজনক এবং পুনরাবৃত্তিমূলক মনে হতে পারে। Listing 7-7-এ, আমরা add_to_waitlist ফাংশনে যাওয়ার জন্য অ্যাবসোলিউট পাথ বা রিলেটিভ পাথ যাই বেছে নিই না কেন, প্রতিবার যখন আমরা add_to_waitlist কল করতে চেয়েছি, তখনও আমাদের front_of_house এবং hosting উল্লেখ করতে হয়েছে। সৌভাগ্যবশত, এই প্রক্রিয়াটিকে সহজ করার একটি উপায় রয়েছে: আমরা একবার use কীওয়ার্ড দিয়ে একটি পাথের শর্টকাট তৈরি করতে পারি এবং তারপর স্কোপের অন্য সব জায়গায় ছোট নামটি ব্যবহার করতে পারি।

Listing 7-11-এ, আমরা crate::front_of_house::hosting মডিউলটিকে eat_at_restaurant ফাংশনের স্কোপে নিয়ে আসি যাতে eat_at_restaurant-এ add_to_waitlist ফাংশনটিকে কল করার জন্য আমাদের কেবল hosting::add_to_waitlist উল্লেখ করতে হয়।

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

একটি স্কোপে use এবং একটি পাথ যোগ করা ফাইল সিস্টেমে একটি সিম্বলিক লিঙ্ক (symbolic link) তৈরি করার মতোই। ক্রেট রুটে use crate::front_of_house::hosting যোগ করে, hosting এখন সেই স্কোপে একটি বৈধ নাম, যেন hosting মডিউলটি ক্রেট রুটে সংজ্ঞায়িত করা হয়েছে। use দিয়ে স্কোপে আনা পাথগুলোও অন্য যেকোনো পাথের মতোই গোপনীয়তা পরীক্ষা করে।

লক্ষ্য করুন যে use শুধুমাত্র সেই বিশেষ স্কোপের জন্য শর্টকাট তৈরি করে যেখানে use সংঘটিত হয়। Listing 7-12 eat_at_restaurant ফাংশনটিকে customer নামক একটি নতুন চাইল্ড মডিউলে সরিয়ে দেয়, যেটি তখন use স্টেটমেন্ট থেকে আলাদা একটি স্কোপ, তাই ফাংশন বডি কম্পাইল হবে না।

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        hosting::add_to_waitlist();
    }
}

কম্পাইলার এরর দেখায় যে শর্টকাটটি আর customer মডিউলের মধ্যে প্রযোজ্য নয়:

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of undeclared crate or module `hosting`
  --> src/lib.rs:11:9
   |
11 |         hosting::add_to_waitlist();
   |         ^^^^^^^ use of undeclared crate or module `hosting`
   |
help: consider importing this module through its public re-export
   |
10 +     use crate::hosting;
   |

warning: unused import: `crate::front_of_house::hosting`
 --> src/lib.rs:7:5
  |
7 | use crate::front_of_house::hosting;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted

লক্ষ্য করুন যে একটি ওয়ার্নিংও রয়েছে যে use আর তার স্কোপে ব্যবহৃত হচ্ছে না! এই সমস্যাটি সমাধান করতে, use-কেও customer মডিউলের মধ্যে সরিয়ে নিন, অথবা চাইল্ড customer মডিউলের মধ্যে super::hosting দিয়ে প্যারেন্ট মডিউলের শর্টকাটটিকে রেফারেন্স করুন।

ইডিওমেটিক use পাথ তৈরি করা (Creating Idiomatic use Paths)

Listing 7-11-এ, আপনি হয়তো ভাবছেন কেন আমরা use crate::front_of_house::hosting নির্দিষ্ট করেছি এবং তারপর eat_at_restaurant-এ hosting::add_to_waitlist কল করেছি, Listing 7-13-এর মতো একই ফলাফল অর্জন করার জন্য add_to_waitlist ফাংশন পর্যন্ত পুরো use পাথ নির্দিষ্ট না করে।

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
}

যদিও Listing 7-11 এবং Listing 7-13 উভয়ই একই কাজ সম্পন্ন করে, Listing 7-11 হল use দিয়ে একটি ফাংশনকে স্কোপে আনার ইডিওমেটিক উপায়। use দিয়ে ফাংশনের প্যারেন্ট মডিউলটিকে স্কোপে আনার অর্থ হল ফাংশনটি কল করার সময় আমাদের প্যারেন্ট মডিউলটি নির্দিষ্ট করতে হবে। ফাংশনটি কল করার সময় প্যারেন্ট মডিউলটি নির্দিষ্ট করা এটি স্পষ্ট করে যে ফাংশনটি লোকালি সংজ্ঞায়িত নয়, তবুও সম্পূর্ণ পাথের পুনরাবৃত্তি কমিয়ে আনা হয়। Listing 7-13-এর কোডটি অস্পষ্ট যে add_to_waitlist কোথায় সংজ্ঞায়িত করা হয়েছে।

অন্যদিকে, use দিয়ে স্ট্রাকট, এনাম এবং অন্যান্য আইটেম আনার সময়, সম্পূর্ণ পাথ নির্দিষ্ট করা ইডিওমেটিক। Listing 7-14 স্ট্যান্ডার্ড লাইব্রেরির HashMap স্ট্রাকটটিকে একটি বাইনারি ক্রেটের স্কোপে আনার ইডিওমেটিক উপায় দেখায়।

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

এই ইডিয়মের পিছনে কোনো জোরালো কারণ নেই: এটি কেবল সেই রীতি যা বিকশিত হয়েছে এবং লোকেরা এইভাবে Rust কোড পড়তে এবং লিখতে অভ্যস্ত হয়ে উঠেছে।

এই ইডিয়মের ব্যতিক্রম হল যদি আমরা use স্টেটমেন্ট দিয়ে একই নামের দুটি আইটেমকে স্কোপে আনি, কারণ Rust এটির অনুমতি দেয় না। Listing 7-15 দেখায় কিভাবে একই নামের কিন্তু ভিন্ন প্যারেন্ট মডিউল সহ দুটি Result টাইপকে স্কোপে আনতে হয় এবং কীভাবে সেগুলোকে রেফার করতে হয়।

use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --snip--
    Ok(())
}

fn function2() -> io::Result<()> {
    // --snip--
    Ok(())
}

আপনি যেমন দেখতে পাচ্ছেন, প্যারেন্ট মডিউলগুলো ব্যবহার করে দুটি Result টাইপকে আলাদা করা হয়। যদি পরিবর্তে আমরা use std::fmt::Result এবং use std::io::Result নির্দিষ্ট করতাম, তাহলে আমাদের একই স্কোপে দুটি Result টাইপ থাকত এবং যখন আমরা Result ব্যবহার করতাম তখন Rust জানত না আমরা কোনটি বোঝাতে চেয়েছি।

as কীওয়ার্ড দিয়ে নতুন নাম প্রদান করা (Providing New Names with the as Keyword)

use দিয়ে একই নামের দুটি টাইপকে একই স্কোপে আনার সমস্যার আরেকটি সমাধান রয়েছে: পাথের পরে, আমরা as এবং টাইপের জন্য একটি নতুন লোকাল নাম বা এলিয়াস (alias) নির্দিষ্ট করতে পারি। Listing 7-16 Listing 7-15-এর কোডটি লেখার আরেকটি উপায় দেখায়, as ব্যবহার করে দুটি Result টাইপের মধ্যে একটির নাম পরিবর্তন করে।

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}

দ্বিতীয় use স্টেটমেন্টে, আমরা std::io::Result টাইপের জন্য নতুন নাম IoResult বেছে নিয়েছি, যেটি std::fmt থেকে আনা Result-এর সাথে সাংঘর্ষিক হবে না। Listing 7-15 এবং Listing 7-16 ইডিওমেটিক হিসাবে বিবেচিত হয়, তাই পছন্দ আপনার উপর নির্ভর করে!

pub use দিয়ে নামগুলো পুনরায় এক্সপোর্ট করা (Re-exporting Names with pub use)

যখন আমরা use কীওয়ার্ড দিয়ে একটি নাম স্কোপে আনি, তখন নতুন স্কোপে উপলব্ধ নামটি প্রাইভেট হয়। আমাদের কোডকে কল করা কোডটিকে সেই নামটি রেফার করার অনুমতি দেওয়ার জন্য যেন এটি সেই কোডের স্কোপে সংজ্ঞায়িত করা হয়েছে, আমরা pub এবং use একত্রিত করতে পারি। এই কৌশলটিকে রি-এক্সপোর্টিং (re-exporting) বলা হয় কারণ আমরা একটি আইটেমকে স্কোপে আনছি কিন্তু সেই আইটেমটিকে অন্যদের তাদের স্কোপে আনার জন্যও উপলব্ধ করছি।

Listing 7-17 Listing 7-11-এর কোডটি দেখায় যেখানে রুট মডিউলে use-কে pub use-এ পরিবর্তন করা হয়েছে।

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

এই পরিবর্তনের আগে, এক্সটার্নাল কোডকে add_to_waitlist ফাংশনটিকে restaurant::front_of_house::hosting::add_to_waitlist() পাথ ব্যবহার করে কল করতে হত, যেটিতে front_of_house মডিউলটিকেও pub হিসাবে চিহ্নিত করার প্রয়োজন হত। এখন যে এই pub use রুট মডিউল থেকে hosting মডিউলটিকে পুনরায় এক্সপোর্ট করেছে, এক্সটার্নাল কোড পরিবর্তে restaurant::hosting::add_to_waitlist() পাথ ব্যবহার করতে পারে।

রি-এক্সপোর্টিং দরকারী যখন আপনার কোডের অভ্যন্তরীণ কাঠামো আপনার কোডকে কল করা প্রোগ্রামাররা ডোমেন সম্পর্কে যেভাবে ভাবেন তার থেকে ভিন্ন হয়। উদাহরণস্বরূপ, এই রেস্তোরাঁর রূপকটিতে, রেস্তোরাঁ চালানো লোকেরা "ফ্রন্ট অফ হাউস" এবং "ব্যাক অফ হাউস" সম্পর্কে চিন্তা করে। কিন্তু একটি রেস্তোরাঁয় আসা গ্রাহকরা সম্ভবত রেস্তোরাঁর অংশগুলো সম্পর্কে সেই পরিভাষায় ভাববেন না। pub use দিয়ে, আমরা আমাদের কোড একটি কাঠামো দিয়ে লিখতে পারি কিন্তু একটি ভিন্ন কাঠামো এক্সপোজ করতে পারি। এটি করলে আমাদের লাইব্রেরিটি লাইব্রেরিতে কাজ করা প্রোগ্রামার এবং লাইব্রেরি কল করা প্রোগ্রামার উভয়ের জন্যই সুসংগঠিত হয়। আমরা চ্যাপ্টার 14-এর pub use দিয়ে একটি সুবিধাজনক পাবলিক API এক্সপোর্ট করা”-তে pub use-এর আরেকটি উদাহরণ এবং এটি কীভাবে আপনার ক্রেটের ডকুমেন্টেশনকে প্রভাবিত করে তা দেখব।

এক্সটার্নাল প্যাকেজ ব্যবহার করা (Using External Packages)

চ্যাপ্টার ২-এ, আমরা একটি অনুমান করার গেম প্রোজেক্ট প্রোগ্রাম করেছি যা র‍্যান্ডম সংখ্যা পেতে rand নামক একটি এক্সটার্নাল প্যাকেজ ব্যবহার করেছে। আমাদের প্রোজেক্টে rand ব্যবহার করার জন্য, আমরা Cargo.toml-এ এই লাইনটি যোগ করেছি:

rand = "0.8.5"

Cargo.toml-এ rand-কে ডিপেন্ডেন্সি হিসাবে যোগ করা Cargo-কে crates.io থেকে rand প্যাকেজ এবং যেকোনো ডিপেন্ডেন্সি ডাউনলোড করতে এবং rand-কে আমাদের প্রোজেক্টের জন্য উপলব্ধ করতে বলে।

তারপর, rand সংজ্ঞাগুলোকে আমাদের প্যাকেজের স্কোপে আনতে, আমরা use লাইন যোগ করেছি যা ক্রেটের নাম, rand দিয়ে শুরু হয় এবং আমরা যে আইটেমগুলোকে স্কোপে আনতে চেয়েছিলাম সেগুলো তালিকাভুক্ত করেছি। “একটি র‍্যান্ডম সংখ্যা তৈরি করা”-তে স্মরণ করুন যে, চ্যাপ্টার ২-এ, আমরা Rng ট্রেইটটিকে স্কোপে এনেছি এবং rand::thread_rng ফাংশনটিকে কল করেছি:

use std::io;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}

Rust কমিউনিটির সদস্যরা crates.io-তে অনেকগুলি প্যাকেজ উপলব্ধ করেছেন এবং সেগুলোর যেকোনোটিকে আপনার প্যাকেজে যুক্ত করার জন্য একই ধাপগুলো জড়িত: সেগুলোকে আপনার প্যাকেজের Cargo.toml ফাইলে তালিকাভুক্ত করা এবং তাদের ক্রেট থেকে আইটেমগুলোকে স্কোপে আনতে use ব্যবহার করা।

মনে রাখবেন যে স্ট্যান্ডার্ড লাইব্রেরি (std)ও আমাদের প্যাকেজের জন্য একটি এক্সটার্নাল ক্রেট। যেহেতু স্ট্যান্ডার্ড লাইব্রেরিটি Rust ভাষার সাথে পাঠানো হয়, তাই আমাদের std অন্তর্ভুক্ত করার জন্য Cargo.toml পরিবর্তন করার প্রয়োজন নেই। কিন্তু আমাদের প্যাকেজের স্কোপে সেখান থেকে আইটেমগুলো আনতে use দিয়ে এটিকে রেফার করতে হবে। উদাহরণস্বরূপ, HashMap-এর সাথে আমরা এই লাইনটি ব্যবহার করব:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
}

এটি স্ট্যান্ডার্ড লাইব্রেরি ক্রেটের নাম std দিয়ে শুরু হওয়া একটি অ্যাবসোলিউট পাথ।

বড় use তালিকা পরিষ্কার করতে নেস্টেড পাথ ব্যবহার করা (Using Nested Paths to Clean Up Large use Lists)

যদি আমরা একই ক্রেট বা একই মডিউলে সংজ্ঞায়িত একাধিক আইটেম ব্যবহার করি, তাহলে প্রতিটি আইটেমকে নিজস্ব লাইনে তালিকাভুক্ত করা আমাদের ফাইলগুলোতে অনেক উল্লম্ব জায়গা নিতে পারে। উদাহরণস্বরূপ, Listing 2-4-এ অনুমান করার গেমে আমাদের দুটি use স্টেটমেন্ট ছিল যা std থেকে আইটেমগুলোকে স্কোপে এনেছিল:

use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

পরিবর্তে, আমরা একই আইটেমগুলোকে এক লাইনে স্কোপে আনতে নেস্টেড পাথ ব্যবহার করতে পারি। আমরা এটি করি পাথের সাধারণ অংশটি নির্দিষ্ট করে, তারপর দুটি কোলন এবং তারপর কার্লি ব্র্যাকেটের মধ্যে পাথের ভিন্ন অংশগুলোর একটি তালিকা, যেমনটি Listing 7-18-এ দেখানো হয়েছে।

use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    let guess: u32 = guess.trim().parse().expect("Please type a number!");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

বড় প্রোগ্রামগুলোতে, একই ক্রেট বা মডিউল থেকে অনেকগুলো আইটেমকে স্কোপে আনতে নেস্টেড পাথ ব্যবহার করা প্রয়োজনীয় use স্টেটমেন্টের সংখ্যা অনেক কমাতে পারে!

আমরা একটি পাথের যেকোনো স্তরে একটি নেস্টেড পাথ ব্যবহার করতে পারি, যা দুটি use স্টেটমেন্টকে একত্রিত করার সময় দরকারী যেখানে একটি সাবপাথ শেয়ার করা হয়। উদাহরণস্বরূপ, Listing 7-19 দুটি use স্টেটমেন্ট দেখায়: একটি যা std::io-কে স্কোপে আনে এবং একটি যা std::io::Write-কে স্কোপে আনে।

use std::io;
use std::io::Write;

এই দুটি পাথের সাধারণ অংশ হল std::io, এবং সেটি হল সম্পূর্ণ প্রথম পাথ। এই দুটি পাথকে একটি use স্টেটমেন্টে মার্জ করতে, আমরা নেস্টেড পাথে self ব্যবহার করতে পারি, যেমনটি Listing 7-20-তে দেখানো হয়েছে।

use std::io::{self, Write};

এই লাইনটি std::io এবং std::io::Write-কে স্কোপে নিয়ে আসে।

গ্লোব অপারেটর (The Glob Operator)

আমরা যদি একটি পাথে সংজ্ঞায়িত সমস্ত পাবলিক আইটেমকে স্কোপে আনতে চাই, তাহলে আমরা সেই পাথটি নির্দিষ্ট করতে পারি এবং তারপর * গ্লোব অপারেটরটি ব্যবহার করতে পারি:

#![allow(unused)]
fn main() {
use std::collections::*;
}

এই use স্টেটমেন্টটি std::collections-এ সংজ্ঞায়িত সমস্ত পাবলিক আইটেমকে বর্তমান স্কোপে নিয়ে আসে। গ্লোব অপারেটর ব্যবহার করার সময় সতর্ক থাকুন! গ্লোব কোন নামগুলো স্কোপে রয়েছে এবং আপনার প্রোগ্রামে ব্যবহৃত একটি নাম কোথায় সংজ্ঞায়িত করা হয়েছিল তা বলা কঠিন করে তুলতে পারে।

গ্লোব অপারেটরটি প্রায়শই পরীক্ষার সময় ব্যবহার করা হয় যাতে পরীক্ষার অধীনে থাকা সমস্ত কিছুকে tests মডিউলে আনা যায়; আমরা চ্যাপ্টার 11-এর “কিভাবে পরীক্ষা লিখতে হয়”-তে এটি নিয়ে কথা বলব। গ্লোব অপারেটরটি কখনও কখনও প্রেলিউড প্যাটার্নের (prelude pattern) অংশ হিসাবেও ব্যবহৃত হয়: সেই প্যাটার্ন সম্পর্কে আরও তথ্যের জন্য স্ট্যান্ডার্ড লাইব্রেরি ডকুমেন্টেশন দেখুন।