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

অনুমান করার গেম প্রোগ্রামিং

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

আমরা একটি ক্লাসিক প্রোগ্রামিং সমস্যা—অনুমান করার গেম (guessing game)—তৈরি করব। এটি যেভাবে কাজ করবে তা হলো: প্রোগ্রামটি ১ থেকে ১০০ এর মধ্যে একটি র‍্যান্ডম পূর্ণসংখ্যা (integer) তৈরি করবে। এরপর এটি প্লেয়ারকে একটি অনুমান প্রবেশ করানোর জন্য বলবে। একটি অনুমান প্রবেশ করানোর পর, প্রোগ্রামটি জানাবে যে অনুমানটি খুব কম নাকি খুব বেশি। যদি অনুমান সঠিক হয়, গেমটি একটি অভিনন্দন বার্তা প্রিন্ট করে খেলা শেষ করে দেবে।

নতুন প্রজেক্ট সেটআপ করা

একটি নতুন প্রজেক্ট সেটআপ করতে, আপনি অধ্যায় ১-এ যে projects ডিরেক্টরি তৈরি করেছিলেন সেখানে যান এবং কার্গো ব্যবহার করে একটি নতুন প্রজেক্ট তৈরি করুন, যেমন:

$ cargo new guessing_game
$ cd guessing_game

প্রথম কমান্ড, cargo new, প্রজেক্টের নাম (guessing_game) প্রথম আর্গুমেন্ট হিসেবে নেয়। দ্বিতীয় কমান্ডটি নতুন প্রজেক্টের ডিরেক্টরিতে পরিবর্তন করে।

তৈরি হওয়া Cargo.toml ফাইলটি দেখুন:

ফাইলের নাম: Cargo.toml

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2024"

[dependencies]

যেমনটি আপনি অধ্যায় ১-এ দেখেছেন, cargo new আপনার জন্য একটি "Hello, world!" প্রোগ্রাম তৈরি করে। src/main.rs ফাইলটি দেখুন:

ফাইলের নাম: src/main.rs

fn main() {
    println!("Hello, world!");
}

এখন চলুন এই "Hello, world!" প্রোগ্রামটি কম্পাইল করি এবং cargo run কমান্ড ব্যবহার করে একই সাথে রান করি:

$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
     Running `target/debug/guessing_game`
Hello, world!

যখন কোনো প্রজেক্টে দ্রুত পরিবর্তন ও পরীক্ষা করার প্রয়োজন হয়, তখন run কমান্ডটি খুব কাজে আসে, যেমনটা আমরা এই গেমে করব—প্রতিটি ধাপ দ্রুত পরীক্ষা করে পরবর্তী ধাপে এগিয়ে যাব।

src/main.rs ফাইলটি আবার খুলুন। আপনি সমস্ত কোড এই ফাইলেই লিখবেন।

একটি অনুমান প্রসেস করা

অনুমান করার গেম প্রোগ্রামের প্রথম অংশে ব্যবহারকারীর কাছ থেকে ইনপুট চাওয়া হবে, সেই ইনপুট প্রসেস করা হবে এবং ইনপুটটি প্রত্যাশিত বিন্যাসে আছে কিনা তা পরীক্ষা করা হবে। শুরু করার জন্য, আমরা প্লেয়ারকে একটি অনুমান ইনপুট করার সুযোগ দেব। লিস্টিং ২-১ এর কোডটি src/main.rs ফাইলে লিখুন।

use std::io;

fn main() {
    println!("Guess the 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}");
}

এই কোডে অনেক তথ্য রয়েছে, তাই চলুন লাইন বাই লাইন আলোচনা করা যাক। ব্যবহারকারীর ইনপুট পেতে এবং তারপর ফলাফল আউটপুট হিসেবে প্রিন্ট করতে, আমাদের io ইনপুট/আউটপুট লাইব্রেরিটি স্কোপের মধ্যে আনতে হবে। io লাইব্রেরিটি স্ট্যান্ডার্ড লাইব্রেরি থেকে আসে, যা std নামে পরিচিত:

use std::io;

fn main() {
    println!("Guess the 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}");
}

ডিফল্টরূপে, রাস্ট স্ট্যান্ডার্ড লাইব্রেরিতে কিছু আইটেম সংজ্ঞায়িত করে রাখে যা প্রতিটি প্রোগ্রামের স্কোপে নিয়ে আসা হয়। এই সেটটিকে prelude বলা হয়, এবং আপনি স্ট্যান্ডার্ড লাইব্রেরি ডকুমেন্টেশনে এর সবকিছু দেখতে পারেন।

আপনি যদি এমন কোনো টাইপ ব্যবহার করতে চান যা prelude-এ নেই, তবে আপনাকে use স্টেটমেন্ট দিয়ে সেই টাইপটি স্পষ্টভাবে স্কোপে আনতে হবে। std::io লাইব্রেরি ব্যবহার করলে আপনি ব্যবহারকারীর ইনপুট গ্রহণ করার মতো অনেক দরকারি ফিচার পাবেন।

যেমনটি আপনি অধ্যায় ১-এ দেখেছেন, main ফাংশন হল প্রোগ্রামের প্রবেশ বিন্দু:

use std::io;

fn main() {
    println!("Guess the 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}");
}

fn সিনট্যাক্স একটি নতুন ফাংশন ঘোষণা করে; () চিহ্ন দিয়ে বোঝানো হয় যে এর কোনো প্যারামিটার নেই; এবং { চিহ্নটি ফাংশনের বডি শুরু করে।

আপনি অধ্যায় ১-এ এটাও শিখেছেন যে, println! একটি ম্যাক্রো যা স্ক্রিনে একটি স্ট্রিং প্রিন্ট করে:

use std::io;

fn main() {
    println!("Guess the 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}");
}

এই কোডটি একটি প্রম্পট প্রিন্ট করছে যা গেমটি কী তা জানাচ্ছে এবং ব্যবহারকারীর কাছ থেকে ইনপুট চাইছে।

ভ্যারিয়েবলের মাধ্যমে মান সংরক্ষণ করা

এরপর, আমরা ব্যবহারকারীর ইনপুট সংরক্ষণের জন্য একটি variable তৈরি করব, এভাবে:

use std::io;

fn main() {
    println!("Guess the 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}");
}

এখন প্রোগ্রামটি আকর্ষণীয় হয়ে উঠছে! এই ছোট লাইনে অনেক কিছু ঘটছে। আমরা ভ্যারিয়েবল তৈরি করতে let স্টেটমেন্ট ব্যবহার করি। এখানে আরেকটি উদাহরণ:

let apples = 5;

এই লাইনটি apples নামে একটি নতুন ভ্যারিয়েবল তৈরি করে এবং এটিকে 5 মানের সাথে বাইন্ড করে। রাস্ট-এ, ভ্যারিয়েবলগুলো ডিফল্টরূপে অপরিবর্তনীয় (immutable), যার মানে একবার আমরা ভ্যারিয়েবলে একটি মান দিলে, সেই মান আর পরিবর্তন হবে না। আমরা এই ধারণাটি অধ্যায় ৩-এর "Variables and Mutability" বিভাগে বিস্তারিতভাবে আলোচনা করব। একটি ভ্যারিয়েবলকে পরিবর্তনযোগ্য (mutable) করতে, আমরা ভ্যারিয়েবলের নামের আগে mut যোগ করি:

let apples = 5; // immutable
let mut bananas = 5; // mutable

দ্রষ্টব্য: // সিনট্যাক্স একটি কমেন্ট শুরু করে যা লাইনের শেষ পর্যন্ত চলে। রাস্ট কমেন্টের মধ্যে থাকা সবকিছু উপেক্ষা করে। আমরা অধ্যায় ৩-এ কমেন্ট সম্পর্কে আরও বিস্তারিত আলোচনা করব।

guessing game প্রোগ্রামে ফিরে আসা যাক, আপনি এখন জানেন যে let mut guess একটি পরিবর্তনযোগ্য ভ্যারিয়েবল guess তৈরি করবে। সমান চিহ্ন (=) রাস্টকে বলে যে আমরা এখন ভ্যারিয়েবলের সাথে কিছু একটা বাইন্ড করতে চাই। সমান চিহ্নের ডানদিকে guess-এর মান রয়েছে, যা String::new কল করার ফলাফল, এটি একটি ফাংশন যা String-এর একটি নতুন ইনস্ট্যান্স প্রদান করে। String হল একটি স্ট্রিং টাইপ যা স্ট্যান্ডার্ড লাইব্রেরি দ্বারা সরবরাহ করা হয় এবং এটি একটি প্রসারণযোগ্য, UTF-8 এনকোডেড টেক্সট।

::new লাইনে :: সিনট্যাক্সটি নির্দেশ করে যে new হল String টাইপের একটি associated function। একটি associated function হলো এমন একটি ফাংশন যা একটি টাইপের উপর ইমপ্লিমেন্ট করা হয়, এই ক্ষেত্রে String। এই new ফাংশনটি একটি নতুন, খালি স্ট্রিং তৈরি করে। আপনি অনেক টাইপের উপরেই একটি new ফাংশন খুঁজে পাবেন কারণ এটি কোনো কিছুর নতুন মান তৈরি করার জন্য একটি সাধারণ নাম।

পুরো কথায়, let mut guess = String::new(); লাইনটি একটি পরিবর্তনযোগ্য ভ্যারিয়েবল তৈরি করেছে যা বর্তমানে একটি String-এর নতুন, খালি ইনস্ট্যান্সের সাথে বাইন্ড করা আছে। যাক!

ব্যবহারকারীর ইনপুট গ্রহণ করা

স্মরণ করুন যে আমরা প্রোগ্রামের প্রথম লাইনে use std::io; দিয়ে স্ট্যান্ডার্ড লাইব্রেরি থেকে ইনপুট/আউটপুট কার্যকারিতা অন্তর্ভুক্ত করেছিলাম। এখন আমরা io মডিউল থেকে stdin ফাংশনটি কল করব, যা আমাদের ব্যবহারকারীর ইনপুট পরিচালনা করতে দেবে:

use std::io;

fn main() {
    println!("Guess the 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}");
}

যদি আমরা প্রোগ্রামের শুরুতে use std::io; দিয়ে io মডিউল ইম্পোর্ট না করতাম, আমরা এখনও ফাংশনটি std::io::stdin হিসেবে লিখে ব্যবহার করতে পারতাম। stdin ফাংশনটি std::io::Stdin এর একটি ইনস্ট্যান্স প্রদান করে, যা আপনার টার্মিনালের স্ট্যান্ডার্ড ইনপুটের একটি হ্যান্ডেলকে প্রতিনিধিত্ব করে।

এরপর, .read_line(&mut guess) লাইনটি ব্যবহারকারীর কাছ থেকে ইনপুট পাওয়ার জন্য স্ট্যান্ডার্ড ইনপুট হ্যান্ডেলের উপর read_line মেথড কল করে। আমরা &mut guess কে read_line এর আর্গুমেন্ট হিসেবে পাস করছি, যাতে এটি জানতে পারে ব্যবহারকারীর ইনপুট কোন স্ট্রিং-এ সংরক্ষণ করতে হবে। read_line-এর পুরো কাজটি হলো ব্যবহারকারী স্ট্যান্ডার্ড ইনপুটে যা টাইপ করে তা একটি স্ট্রিং-এ যুক্ত করা (এর বিষয়বস্তু ওভাররাইট না করে), তাই আমরা সেই স্ট্রিংটিকে একটি আর্গুমেন্ট হিসাবে পাস করি। স্ট্রিং আর্গুমেন্টটি পরিবর্তনযোগ্য হতে হবে যাতে মেথডটি স্ট্রিং এর বিষয়বস্তু পরিবর্তন করতে পারে।

& চিহ্নটি নির্দেশ করে যে এই আর্গুমেন্টটি একটি reference, যা আপনার কোডের একাধিক অংশকে মেমরিতে একাধিকবার ডেটা কপি না করে একই ডেটা অ্যাক্সেস করার একটি উপায় দেয়। Reference একটি জটিল বৈশিষ্ট্য, এবং রাস্টের অন্যতম প্রধান সুবিধা হল reference ব্যবহার করা কতটা নিরাপদ এবং সহজ। এই প্রোগ্রামটি শেষ করার জন্য আপনাকে সেই সব বিস্তারিত জানতে হবে না। আপাতত, আপনাকে শুধু জানতে হবে যে, ভ্যারিয়েবলের মতো, reference-ও ডিফল্টরূপে অপরিবর্তনীয়। তাই, এটিকে পরিবর্তনযোগ্য করার জন্য আপনাকে &guess এর পরিবর্তে &mut guess লিখতে হবে। (অধ্যায় ৪ reference সম্পর্কে আরও বিস্তারিত ব্যাখ্যা করবে।)

Result দিয়ে সম্ভাব্য ব্যর্থতা সামলানো

আমরা এখনও এই কোড লাইনের উপর কাজ করছি। আমরা এখন তৃতীয় একটি টেক্সট লাইন নিয়ে আলোচনা করছি, কিন্তু লক্ষ্য করুন যে এটি এখনও একটি একক যৌক্তিক কোড লাইনের অংশ। পরবর্তী অংশটি হল এই মেথড:

use std::io;

fn main() {
    println!("Guess the 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}");
}

আমরা এই কোডটি এভাবেও লিখতে পারতাম:

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

তবে, একটি দীর্ঘ লাইন পড়া কঠিন, তাই এটিকে ভাগ করা ভালো। যখন আপনি .method_name() সিনট্যাক্স দিয়ে একটি মেথড কল করেন, তখন দীর্ঘ লাইনগুলো ভাঙতে একটি নতুন লাইন এবং অন্যান্য হোয়াইটস্পেস ব্যবহার করা প্রায়শই বুদ্ধিমানের কাজ। এখন চলুন আলোচনা করি এই লাইনটি কী করে।

যেমন আগে উল্লেখ করা হয়েছে, read_line ব্যবহারকারীর প্রবেশ করানো সবকিছুকে আমরা যে স্ট্রিংটি পাস করি তাতে রাখে, কিন্তু এটি একটি Result মানও প্রদান করে। Result হল একটি enumeration, যাকে প্রায়ই enum বলা হয়, যা এমন একটি টাইপ যা একাধিক সম্ভাব্য অবস্থায় থাকতে পারে। আমরা প্রতিটি সম্ভাব্য অবস্থাকে একটি variant বলি।

অধ্যায় ৬ এ enum সম্পর্কে আরও বিস্তারিত আলোচনা করা হবে। এই Result টাইপগুলোর উদ্দেশ্য হল ত্রুটি-হ্যান্ডলিং তথ্য এনকোড করা।

Result-এর variant-গুলো হল Ok এবং ErrOk variant নির্দেশ করে যে অপারেশনটি সফল হয়েছে, এবং এতে সফলভাবে তৈরি হওয়া মানটি থাকে। Err variant মানে অপারেশনটি ব্যর্থ হয়েছে, এবং এতে অপারেশনটি কীভাবে বা কেন ব্যর্থ হয়েছে সে সম্পর্কে তথ্য থাকে।

যেকোনো টাইপের মানের মতোই, Result টাইপের মানের উপর মেথড সংজ্ঞায়িত থাকে। Result এর একটি ইনস্ট্যান্সের একটি expect মেথড রয়েছে যা আপনি কল করতে পারেন। যদি Result এর এই ইনস্ট্যান্সটি একটি Err মান হয়, expect প্রোগ্রামটি ক্র্যাশ করাবে এবং আপনি expect-এর আর্গুমেন্ট হিসেবে যে বার্তাটি পাস করেছেন তা প্রদর্শন করবে। যদি read_line মেথড একটি Err প্রদান করে, তবে এটি সম্ভবত অন্তর্নিহিত অপারেটিং সিস্টেম থেকে আসা একটি ত্রুটির ফল। যদি Result এর এই ইনস্ট্যান্সটি একটি Ok মান হয়, expect Ok যে রিটার্ন মানটি ধারণ করছে তা নেবে এবং শুধুমাত্র সেই মানটি আপনাকে ফেরত দেবে যাতে আপনি এটি ব্যবহার করতে পারেন। এই ক্ষেত্রে, সেই মানটি হল ব্যবহারকারীর ইনপুটের বাইটের সংখ্যা।

আপনি যদি expect কল না করেন, প্রোগ্রামটি কম্পাইল হবে, কিন্তু আপনি একটি সতর্কবার্তা পাবেন:

$ cargo build
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
warning: unused `Result` that must be used
  --> src/main.rs:10:5
   |
10 |     io::stdin().read_line(&mut guess);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this `Result` may be an `Err` variant, which should be handled
   = note: `#[warn(unused_must_use)]` on by default
help: use `let _ = ...` to ignore the resulting value
   |
10 |     let _ = io::stdin().read_line(&mut guess);
   |     +++++++

warning: `guessing_game` (bin "guessing_game") generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.59s

রাস্ট সতর্ক করে যে আপনি read_line থেকে প্রাপ্ত Result মানটি ব্যবহার করেননি, যা নির্দেশ করে যে প্রোগ্রামটি একটি সম্ভাব্য ত্রুটি পরিচালনা করেনি।

সতর্কবার্তাটি দূর করার সঠিক উপায় হলো আসলে ত্রুটি-হ্যান্ডলিং কোড লেখা, কিন্তু আমাদের ক্ষেত্রে আমরা শুধু চাই যে কোনো সমস্যা হলে প্রোগ্রামটি ক্র্যাশ করুক, তাই আমরা expect ব্যবহার করতে পারি। আপনি ত্রুটি থেকে পুনরুদ্ধার সম্পর্কে অধ্যায় ৯ এ শিখবেন।

println! প্লেসহোল্ডার দিয়ে মান প্রিন্ট করা

শেষের কার্লি ব্র্যাকেট বাদে, এখন পর্যন্ত কোডে আলোচনার জন্য আর মাত্র একটি লাইন বাকি আছে:

use std::io;

fn main() {
    println!("Guess the 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}");
}

এই লাইনটি সেই স্ট্রিংটি প্রিন্ট করে যা এখন ব্যবহারকারীর ইনপুট ধারণ করে। {} কার্লি ব্র্যাকেটের সেট একটি প্লেসহোল্ডার: {}-কে ছোট কাঁকড়ার চিমটার মতো ভাবুন যা একটি মানকে ধরে রাখে। একটি ভ্যারিয়েবলের মান প্রিন্ট করার সময়, ভ্যারিয়েবলের নামটি কার্লি ব্র্যাকেটের ভিতরে যেতে পারে। একটি এক্সপ্রেশন মূল্যায়ন করার ফলাফল প্রিন্ট করার সময়, ফরম্যাট স্ট্রিং-এ খালি কার্লি ব্র্যাকেট রাখুন, তারপর ফরম্যাট স্ট্রিং এর পরে একটি কমা-বিভক্ত এক্সপ্রেশনের তালিকা দিন যা প্রতিটি খালি কার্লি ব্র্যাকেট প্লেসহোল্ডারে একই ক্রমে প্রিন্ট হবে। একটি println! কলে একটি ভ্যারিয়েবল এবং একটি এক্সপ্রেশনের ফলাফল প্রিন্ট করাটা এমন দেখাবে:

#![allow(unused)]
fn main() {
let x = 5;
let y = 10;

println!("x = {x} and y + 2 = {}", y + 2);
}

এই কোডটি x = 5 and y + 2 = 12 প্রিন্ট করবে।

প্রথম অংশ পরীক্ষা করা

চলুন guessing game-এর প্রথম অংশটি পরীক্ষা করি। এটি cargo run ব্যবহার করে চালান:

$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.44s
     Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6

এই মুহূর্তে, গেমের প্রথম অংশটি সম্পন্ন হয়েছে: আমরা কীবোর্ড থেকে ইনপুট পাচ্ছি এবং তারপর তা প্রিন্ট করছি।

একটি গোপন সংখ্যা তৈরি করা

এরপর, আমাদের একটি গোপন সংখ্যা তৈরি করতে হবে যা ব্যবহারকারী অনুমান করার চেষ্টা করবে। গোপন সংখ্যাটি প্রতিবার ভিন্ন হওয়া উচিত যাতে গেমটি একাধিকবার খেলতে মজা লাগে। আমরা ১ থেকে ১০০ এর মধ্যে একটি র‍্যান্ডম সংখ্যা ব্যবহার করব যাতে গেমটি খুব বেশি কঠিন না হয়। রাস্ট এখনও তার স্ট্যান্ডার্ড লাইব্রেরিতে র‍্যান্ডম সংখ্যা তৈরির কার্যকারিতা অন্তর্ভুক্ত করেনি। তবে, রাস্ট টিম একটি rand crate সরবরাহ করে যেখানে এই কার্যকারিতা রয়েছে।

আরও কার্যকারিতা পেতে একটি Crate ব্যবহার করা

মনে রাখবেন যে একটি crate হলো রাস্ট সোর্স কোড ফাইলের একটি সংগ্রহ। আমরা যে প্রজেক্টটি তৈরি করছি তা একটি binary crate, যা একটি এক্সিকিউটেবল। rand crate একটি library crate, যা এমন কোড ধারণ করে যা অন্য প্রোগ্রামে ব্যবহারের উদ্দেশ্যে তৈরি এবং এটি নিজে থেকে এক্সিকিউট করা যায় না।

কার্গোর এক্সটার্নাল crate সমন্বয় করার ক্ষমতা এখানেই কার্গোর আসল শক্তি প্রকাশ পায়। rand ব্যবহার করে কোড লেখার আগে, আমাদের Cargo.toml ফাইলটি পরিবর্তন করে rand crate-কে একটি ডিপেন্ডেন্সি হিসেবে অন্তর্ভুক্ত করতে হবে। ফাইলটি এখন খুলুন এবং [dependencies] সেকশন হেডারের নিচে নিম্নলিখিত লাইনটি যোগ করুন যা কার্গো আপনার জন্য তৈরি করেছে। নিশ্চিত করুন যে আপনি rand-কে ঠিক যেমনভাবে আমরা এখানে দিয়েছি, এই ভার্সন নম্বর সহ নির্দিষ্ট করেছেন, অন্যথায় এই টিউটোরিয়ালের কোড উদাহরণগুলো কাজ নাও করতে পারে:

ফাইলের নাম: Cargo.toml

[dependencies]
rand = "0.8.5"

Cargo.toml ফাইলে, একটি হেডারের পরে যা কিছু থাকে তা সেই সেকশনের অংশ যতক্ষণ না অন্য একটি সেকশন শুরু হয়। [dependencies]-তে আপনি কার্গোকে বলেন আপনার প্রজেক্ট কোন এক্সটার্নাল crate-গুলির উপর নির্ভর করে এবং সেই crate-গুলির কোন ভার্সন আপনার প্রয়োজন। এক্ষেত্রে, আমরা rand crate-কে সেমান্টিক ভার্সন স্পেসিফায়ার 0.8.5 দিয়ে নির্দিষ্ট করছি। কার্গো Semantic Versioning (কখনও কখনও SemVer বলা হয়) বোঝে, যা ভার্সন নম্বর লেখার একটি স্ট্যান্ডার্ড। 0.8.5 স্পেসিফায়ারটি আসলে ^0.8.5-এর একটি সংক্ষিপ্ত রূপ, যার মানে হল এমন যেকোনো ভার্সন যা অন্তত 0.8.5 কিন্তু 0.9.0 এর নিচে।

কার্গো এই ভার্সনগুলোকে 0.8.5 ভার্সনের সাথে সামঞ্জস্যপূর্ণ পাবলিক API আছে বলে মনে করে, এবং এই স্পেসিফিকেশন নিশ্চিত করে যে আপনি সর্বশেষ প্যাচ রিলিজ পাবেন যা এখনও এই অধ্যায়ের কোডের সাথে কম্পাইল হবে। 0.9.0 বা তার বেশি যেকোনো ভার্সনের একই API থাকবে এমন কোনো নিশ্চয়তা নেই যা নিম্নলিখিত উদাহরণগুলিতে ব্যবহৃত হয়েছে।

এখন, কোডের কোনো পরিবর্তন না করে, চলুন প্রজেক্টটি বিল্ড করি, যেমনটি লিস্টিং ২-২-এ দেখানো হয়েছে।

$ cargo build
  Updating crates.io index
   Locking 15 packages to latest Rust 1.85.0 compatible versions
    Adding rand v0.8.5 (available: v0.9.0)
 Compiling proc-macro2 v1.0.93
 Compiling unicode-ident v1.0.17
 Compiling libc v0.2.170
 Compiling cfg-if v1.0.0
 Compiling byteorder v1.5.0
 Compiling getrandom v0.2.15
 Compiling rand_core v0.6.4
 Compiling quote v1.0.38
 Compiling syn v2.0.98
 Compiling zerocopy-derive v0.7.35
 Compiling zerocopy v0.7.35
 Compiling ppv-lite86 v0.2.20
 Compiling rand_chacha v0.3.1
 Compiling rand v0.8.5
 Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
  Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.48s```

</Listing>

আপনি ভিন্ন ভার্সন নম্বর দেখতে পারেন (কিন্তু SemVer-এর কারণে সেগুলি সব কোডের সাথে সামঞ্জস্যপূর্ণ হবে) এবং ভিন্ন লাইন দেখতে পারেন (অপারেটিং সিস্টেমের উপর নির্ভর করে), এবং লাইনগুলি ভিন্ন ক্রমে থাকতে পারে।

যখন আমরা একটি এক্সটার্নাল ডিপেন্ডেন্সি অন্তর্ভুক্ত করি, কার্গো সেই ডিপেন্ডেন্সির জন্য প্রয়োজনীয় সমস্ত কিছুর সর্বশেষ ভার্সন _registry_ থেকে নিয়ে আসে, যা [Crates.io][cratesio] থেকে ডেটার একটি কপি। Crates.io হল সেই জায়গা যেখানে রাস্ট ইকোসিস্টেমের মানুষজন তাদের ওপেন সোর্স রাস্ট প্রজেক্টগুলো অন্যদের ব্যবহারের জন্য পোস্ট করে।

রেজিস্ট্রি আপডেট করার পরে, কার্গো `[dependencies]` সেকশন পরীক্ষা করে এবং তালিকাভুক্ত যেকোনো crate যা এখনও ডাউনলোড করা হয়নি তা ডাউনলোড করে। এই ক্ষেত্রে, যদিও আমরা কেবল `rand`-কে একটি ডিপেন্ডেন্সি হিসেবে তালিকাভুক্ত করেছি, কার্গো `rand`-এর কাজ করার জন্য প্রয়োজনীয় অন্যান্য crate-গুলিও নিয়ে এসেছে। crate-গুলি ডাউনলোড করার পরে, রাস্ট সেগুলি কম্পাইল করে এবং তারপর ডিপেন্ডেন্সি সহ প্রজেক্টটি কম্পাইল করে।

আপনি যদি কোনো পরিবর্তন না করে অবিলম্বে আবার `cargo build` চালান, আপনি `Finished` লাইন ছাড়া আর কোনো আউটপুট পাবেন না। কার্গো জানে যে এটি ইতিমধ্যে ডিপেন্ডেন্সিগুলি ডাউনলোড এবং কম্পাইল করেছে, এবং আপনি আপনার _Cargo.toml_ ফাইলে সেগুলি সম্পর্কে কিছু পরিবর্তন করেননি। কার্গো এটাও জানে যে আপনি আপনার কোড সম্পর্কে কিছু পরিবর্তন করেননি, তাই এটি সেটাও পুনরায় কম্পাইল করে না। করার মতো কিছু না থাকায়, এটি কেবল প্রস্থান করে।

আপনি যদি _src/main.rs_ ফাইলটি খোলেন, একটি তুচ্ছ পরিবর্তন করেন, এবং তারপর এটি সংরক্ষণ করে আবার বিল্ড করেন, আপনি কেবল দুটি আউটপুট লাইন দেখতে পাবেন:

<!-- manual-regeneration
cd listings/ch02-guessing-game-tutorial/listing-02-02/
touch src/main.rs
cargo build -->

```console
$ cargo build
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s

এই লাইনগুলি দেখায় যে কার্গো কেবল আপনার src/main.rs ফাইলের ক্ষুদ্র পরিবর্তনের সাথে বিল্ড আপডেট করে। আপনার ডিপেন্ডেন্সিগুলি পরিবর্তিত হয়নি, তাই কার্গো জানে যে এটি সেগুলির জন্য যা ইতিমধ্যে ডাউনলোড এবং কম্পাইল করেছে তা পুনরায় ব্যবহার করতে পারে।

Cargo.lock ফাইলের মাধ্যমে পুনরুৎপাদনযোগ্য বিল্ড নিশ্চিত করা

কার্গোর একটি মেকানিজম রয়েছে যা নিশ্চিত করে যে আপনি বা অন্য কেউ আপনার কোড বিল্ড করার সময় প্রতিবার একই আর্টিফ্যাক্ট পুনর্নির্মাণ করতে পারবেন: কার্গো কেবল আপনার নির্দিষ্ট করা ডিপেন্ডেন্সিগুলির ভার্সন ব্যবহার করবে যতক্ষণ না আপনি অন্যথায় নির্দেশ দেন। উদাহরণস্বরূপ, ধরা যাক আগামী সপ্তাহে rand crate-এর 0.8.6 ভার্সন আসে, এবং সেই ভার্সনে একটি গুরুত্বপূর্ণ বাগ ফিক্স রয়েছে, কিন্তু এতে একটি রিগ্রেশনও রয়েছে যা আপনার কোড ভেঙে দেবে। এটি সামলাতে, রাস্ট প্রথমবার cargo build চালানোর সময় Cargo.lock ফাইল তৈরি করে, তাই এখন আমাদের guessing_game ডিরেক্টরিতে এটি রয়েছে।

যখন আপনি প্রথমবার একটি প্রজেক্ট বিল্ড করেন, কার্গো ডিপেন্ডেন্সিগুলির সমস্ত ভার্সন বের করে যা মানদণ্ড পূরণ করে এবং তারপরে সেগুলি Cargo.lock ফাইলে লেখে। ভবিষ্যতে যখন আপনি আপনার প্রজেক্ট বিল্ড করবেন, কার্গো দেখবে যে Cargo.lock ফাইলটি বিদ্যমান এবং সেখানে নির্দিষ্ট ভার্সনগুলি ব্যবহার করবে, আবার ভার্সন বের করার সমস্ত কাজ না করে। এটি আপনাকে স্বয়ংক্রিয়ভাবে একটি পুনরুৎপাদনযোগ্য বিল্ড পেতে দেয়। অন্য কথায়, আপনার প্রজেক্ট 0.8.5-এ থাকবে যতক্ষণ না আপনি স্পষ্টভাবে আপগ্রেড করেন, Cargo.lock ফাইলের কারণে। যেহেতু Cargo.lock ফাইলটি পুনরুৎপাদনযোগ্য বিল্ডের জন্য গুরুত্বপূর্ণ, তাই এটি প্রায়শই আপনার প্রজেক্টের বাকি কোডের সাথে সোর্স কন্ট্রোলে চেক ইন করা হয়।

একটি নতুন ভার্সন পেতে একটি Crate আপডেট করা

যখন আপনি একটি crate আপডেট করতে চান, কার্গো update কমান্ড সরবরাহ করে, যা Cargo.lock ফাইলটিকে উপেক্ষা করবে এবং Cargo.toml-এ আপনার স্পেসিফিকেশনগুলির সাথে মানানসই সমস্ত সর্বশেষ ভার্সন খুঁজে বের করবে। কার্গো তারপর সেই ভার্সনগুলি Cargo.lock ফাইলে লিখবে। এই ক্ষেত্রে, কার্গো কেবল 0.8.5 এর চেয়ে বড় এবং 0.9.0 এর চেয়ে কম ভার্সন খুঁজবে। যদি rand crate-টি 0.8.6 এবং 0.9.0 এই দুটি নতুন ভার্সন প্রকাশ করে থাকে, তবে আপনি cargo update চালালে নিম্নলিখিতটি দেখতে পাবেন:

$ cargo update
    Updating crates.io index
     Locking 1 package to latest Rust 1.85.0 compatible version
    Updating rand v0.8.5 -> v0.8.6 (available: v0.9.0)

কার্গো 0.9.0 রিলিজটি উপেক্ষা করে। এই মুহূর্তে, আপনি আপনার Cargo.lock ফাইলে একটি পরিবর্তনও লক্ষ্য করবেন যা উল্লেখ করে যে আপনি এখন rand crate-এর যে ভার্সনটি ব্যবহার করছেন তা হল 0.8.6। rand ভার্সন 0.9.0 বা 0.9.x সিরিজের যেকোনো ভার্সন ব্যবহার করতে, আপনাকে Cargo.toml ফাইলটি আপডেট করে এমন দেখতে হবে:

[dependencies]
rand = "0.9.0"

পরবর্তীবার যখন আপনি cargo build চালাবেন, কার্গো উপলব্ধ crate-গুলির রেজিস্ট্রি আপডেট করবে এবং আপনার নির্দিষ্ট করা নতুন ভার্সন অনুযায়ী আপনার rand প্রয়োজনীয়তা পুনরায় মূল্যায়ন করবে।

কার্গো এবং এর ইকোসিস্টেম সম্পর্কে আরও অনেক কিছু বলার আছে, যা আমরা অধ্যায় ১৪-তে আলোচনা করব, কিন্তু আপাতত, আপনার এটুকুই জানা দরকার। কার্গো লাইব্রেরি পুনঃব্যবহার করা খুব সহজ করে তোলে, তাই রাস্টেশিয়ানরা ছোট ছোট প্রজেক্ট লিখতে পারে যা বিভিন্ন প্যাকেজ থেকে একত্রিত হয়।

একটি র‍্যান্ডম সংখ্যা তৈরি করা

চলুন অনুমান করার জন্য একটি সংখ্যা তৈরি করতে rand ব্যবহার করা শুরু করি। পরবর্তী ধাপ হল src/main.rs আপডেট করা, যেমনটি লিস্টিং ২-৩-এ দেখানো হয়েছে।

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}");
}

প্রথমে আমরা use rand::Rng; লাইনটি যোগ করি। Rng ট্রেইটটি র‍্যান্ডম নম্বর জেনারেটর দ্বারা প্রয়োগ করা মেথডগুলিকে সংজ্ঞায়িত করে, এবং এই ট্রেইটটি আমাদের সেই মেথডগুলি ব্যবহার করার জন্য স্কোপে থাকতে হবে। অধ্যায় ১০-এ ট্রেইটগুলি বিস্তারিতভাবে আলোচনা করা হবে।

এরপরে, আমরা মাঝখানে দুটি লাইন যোগ করছি। প্রথম লাইনে, আমরা rand::thread_rng ফাংশনটি কল করি যা আমাদের নির্দিষ্ট র‍্যান্ডম নম্বর জেনারেটর দেয় যা আমরা ব্যবহার করতে যাচ্ছি: এটি বর্তমান এক্সিকিউশন থ্রেডের জন্য স্থানীয় এবং অপারেটিং সিস্টেম দ্বারা সিড করা হয়। তারপরে আমরা র‍্যান্ডম নম্বর জেনারেটরে gen_range মেথডটি কল করি। এই মেথডটি Rng ট্রেইট দ্বারা সংজ্ঞায়িত যা আমরা use rand::Rng; স্টেটমেন্ট দিয়ে স্কোপে এনেছিলাম। gen_range মেথডটি একটি রেঞ্জ এক্সপ্রেশনকে আর্গুমেন্ট হিসেবে নেয় এবং সেই রেঞ্জের মধ্যে একটি র‍্যান্ডম নম্বর তৈরি করে। আমরা এখানে যে ধরনের রেঞ্জ এক্সপ্রেশন ব্যবহার করছি তা start..=end আকারে থাকে এবং এটি নিম্ন এবং উচ্চ উভয় সীমার জন্যই অন্তর্ভুক্ত, তাই ১ থেকে ১০০ এর মধ্যে একটি সংখ্যা অনুরোধ করার জন্য আমাদের 1..=100 নির্দিষ্ট করতে হবে।

দ্রষ্টব্য: আপনি কেবল জেনেই যাবেন না কোন ট্রেইট ব্যবহার করতে হবে এবং কোন মেথড এবং ফাংশনগুলি একটি crate থেকে কল করতে হবে, তাই প্রতিটি crate-এর ডকুমেন্টেশনে এটি ব্যবহারের জন্য নির্দেশাবলী থাকে। কার্গোর আরেকটি চমৎকার বৈশিষ্ট্য হল cargo doc --open কমান্ডটি চালালে এটি আপনার সমস্ত ডিপেন্ডেন্সি দ্বারা সরবরাহ করা ডকুমেন্টেশন স্থানীয়ভাবে তৈরি করবে এবং আপনার ব্রাউজারে খুলবে। আপনি যদি rand crate-এর অন্যান্য কার্যকারিতা সম্পর্কে আগ্রহী হন, উদাহরণস্বরূপ, cargo doc --open চালান এবং বামদিকের সাইডবারে rand-এ ক্লিক করুন।

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

প্রোগ্রামটি কয়েকবার চালানোর চেষ্টা করুন:

$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/guessing_game`
Guess the number!
The secret number is: 7
Please input your guess.
4
You guessed: 4

$ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/guessing_game`
Guess the number!
The secret number is: 83
Please input your guess.
5
You guessed: 5

আপনার বিভিন্ন র‍্যান্ডম সংখ্যা পাওয়া উচিত, এবং সেগুলি সবই ১ থেকে ১০০ এর মধ্যে সংখ্যা হওয়া উচিত। দারুণ কাজ!

অনুমানের সাথে গোপন সংখ্যার তুলনা

এখন আমাদের কাছে ব্যবহারকারীর ইনপুট এবং একটি র‍্যান্ডম সংখ্যা আছে, আমরা তাদের তুলনা করতে পারি। সেই ধাপটি লিস্টিং ২-৪-এ দেখানো হয়েছে। মনে রাখবেন যে এই কোডটি এখনই কম্পাইল হবে না, যেমনটি আমরা ব্যাখ্যা করব।

use std::cmp::Ordering;
use std::io;

use rand::Rng;

fn main() {
    // --snip--
    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!"),
    }
}

প্রথমে আমরা আরেকটি use স্টেটমেন্ট যোগ করি, যা স্ট্যান্ডার্ড লাইব্রেরি থেকে std::cmp::Ordering নামে একটি টাইপ স্কোপে নিয়ে আসে। Ordering টাইপটি আরেকটি enum এবং এর ভ্যারিয়েন্টগুলি হল Less, Greater, এবং Equal। এগুলি হল তিনটি সম্ভাব্য ফলাফল যা আপনি দুটি মান তুলনা করার সময় পেতে পারেন।

তারপর আমরা নীচে পাঁচটি নতুন লাইন যোগ করি যা Ordering টাইপ ব্যবহার করে। cmp মেথড দুটি মান তুলনা করে এবং যা কিছু তুলনা করা যায় তার উপর কল করা যেতে পারে। এটি আপনি যার সাথে তুলনা করতে চান তার একটি রেফারেন্স নেয়: এখানে এটি guess-কে secret_number-এর সাথে তুলনা করছে। তারপর এটি Ordering enum-এর একটি ভ্যারিয়েন্ট প্রদান করে যা আমরা use স্টেটমেন্ট দিয়ে স্কোপে এনেছিলাম। আমরা একটি match এক্সপ্রেশন ব্যবহার করি guess এবং secret_number-এর মানগুলির সাথে cmp-কে কল করার ফলে Ordering-এর কোন ভ্যারিয়েন্টটি ফেরত এসেছে তার উপর ভিত্তি করে পরবর্তী কী করতে হবে তা সিদ্ধান্ত নিতে।

একটি match এক্সপ্রেশন arms (শাখা) দিয়ে গঠিত। একটি arm-এ একটি pattern (প্যাটার্ন) থাকে যার সাথে মেলানো হয়, এবং যে কোডটি চালানো উচিত যদি match-কে দেওয়া মানটি সেই arm-এর প্যাটার্নের সাথে মিলে যায়। রাস্ট match-কে দেওয়া মানটি নেয় এবং প্রতিটি arm-এর প্যাটার্ন একের পর এক পরীক্ষা করে। প্যাটার্ন এবং match কনস্ট্রাক্ট হল শক্তিশালী রাস্ট বৈশিষ্ট্য: এগুলি আপনাকে আপনার কোড সম্মুখীন হতে পারে এমন বিভিন্ন পরিস্থিতি প্রকাশ করতে দেয় এবং তারা নিশ্চিত করে যে আপনি সেগুলি সবই পরিচালনা করেছেন। এই বৈশিষ্ট্যগুলি যথাক্রমে অধ্যায় ৬ এবং অধ্যায় ১৯-এ বিস্তারিতভাবে আলোচনা করা হবে।

চলুন আমরা এখানে যে match এক্সপ্রেশনটি ব্যবহার করছি তার একটি উদাহরণ দিয়ে হেঁটে যাই। ধরা যাক ব্যবহারকারী ৫০ অনুমান করেছে এবং এইবার র‍্যান্ডমভাবে তৈরি গোপন সংখ্যাটি হল ৩৮।

যখন কোডটি ৫০ কে ৩৮ এর সাথে তুলনা করে, cmp মেথডটি Ordering::Greater প্রদান করবে কারণ ৫০, ৩৮ এর চেয়ে বড়। match এক্সপ্রেশনটি Ordering::Greater মানটি পায় এবং প্রতিটি arm-এর প্যাটার্ন পরীক্ষা করা শুরু করে। এটি প্রথম arm-এর প্যাটার্ন, Ordering::Less-এর দিকে তাকায় এবং দেখে যে Ordering::Greater মানটি Ordering::Less-এর সাথে মেলে না, তাই এটি সেই arm-এর কোডটি উপেক্ষা করে এবং পরবর্তী arm-এ চলে যায়। পরবর্তী arm-এর প্যাটার্ন হল Ordering::Greater, যা Ordering::Greater-এর সাথে মিলে যায়! সেই arm-এর সংশ্লিষ্ট কোডটি কার্যকর হবে এবং স্ক্রিনে Too big! প্রিন্ট করবে। match এক্সপ্রেশনটি প্রথম সফল ম্যাচের পরে শেষ হয়ে যায়, তাই এটি এই পরিস্থিতিতে শেষ arm-টি দেখবে না।

তবে, লিস্টিং ২-৪-এর কোডটি এখনও কম্পাইল হবে না। চলুন চেষ্টা করি:

$ cargo build
   Compiling libc v0.2.86
   Compiling getrandom v0.2.2
   Compiling cfg-if v1.0.0
   Compiling ppv-lite86 v0.2.10
   Compiling rand_core v0.6.2
   Compiling rand_chacha v0.3.0
   Compiling rand v0.8.5
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
error[E0308]: mismatched types
  --> src/main.rs:23:21
   |
23 |     match guess.cmp(&secret_number) {
   |                 --- ^^^^^^^^^^^^^^ expected `&String`, found `&{integer}`
   |                 |
   |                 arguments to this method are incorrect
   |
   = note: expected reference `&String`
              found reference `&{integer}`
note: method defined here
  --> /rustc/4eb161250e340c8f48f66e2b929ef4a5bed7c181/library/core/src/cmp.rs:964:8

For more information about this error, try `rustc --explain E0308`.
error: could not compile `guessing_game` (bin "guessing_game") due to 1 previous error

ত্রুটির মূল কথা হল mismatched types (বেমানান টাইপ)। রাস্টের একটি শক্তিশালী, স্ট্যাটিক টাইপ সিস্টেম আছে। তবে, এর টাইপ ইনফারেন্সও আছে। যখন আমরা let mut guess = String::new() লিখেছিলাম, রাস্ট অনুমান করতে পেরেছিল যে guess একটি String হওয়া উচিত এবং আমাদের টাইপ লিখতে বাধ্য করেনি। অন্যদিকে, secret_number একটি সংখ্যা টাইপ। রাস্টের কয়েকটি সংখ্যা টাইপের মান ১ থেকে ১০০ এর মধ্যে থাকতে পারে: i32, একটি ৩২-বিট সংখ্যা; u32, একটি আনসাইন্ড ৩২-বিট সংখ্যা; i64, একটি ৬৪-বিট সংখ্যা; এবং আরও অন্যান্য। অন্যথায় নির্দিষ্ট না করা হলে, রাস্ট ডিফল্ট হিসেবে i32 ব্যবহার করে, যা secret_number-এর টাইপ, যদি না আপনি অন্য কোথাও টাইপ তথ্য যোগ করেন যা রাস্টকে একটি ভিন্ন সংখ্যাসূচক টাইপ অনুমান করতে বাধ্য করবে। ত্রুটির কারণ হল রাস্ট একটি স্ট্রিং এবং একটি সংখ্যা টাইপের তুলনা করতে পারে না।

শেষ পর্যন্ত, আমরা প্রোগ্রামটি ইনপুট হিসাবে যে String পড়ে তা একটি সংখ্যা টাইপে রূপান্তর করতে চাই যাতে আমরা এটিকে গোপন সংখ্যার সাথে সংখ্যাগতভাবে তুলনা করতে পারি। আমরা main ফাংশনের বডিতে এই লাইনটি যোগ করে তা করি:

ফাইলের নাম: src/main.rs

use std::cmp::Ordering;
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.");

    // --snip--

    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!"),
    }
}

লাইনটি হল:

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

আমরা guess নামে একটি ভ্যারিয়েবল তৈরি করি। কিন্তু অপেক্ষা করুন, প্রোগ্রামে কি ইতিমধ্যে guess নামে একটি ভ্যারিয়েবল নেই? আছে, কিন্তু সহায়কভাবে রাস্ট আমাদের guess-এর পূর্ববর্তী মানটিকে একটি নতুন মান দিয়ে শ্যাডো (shadow) করার অনুমতি দেয়। Shadowing আমাদের guess ভ্যারিয়েবলের নামটি পুনরায় ব্যবহার করতে দেয়, যেমন guess_str এবং guess নামে দুটি অনন্য ভ্যারিয়েবল তৈরি করতে বাধ্য করার পরিবর্তে। আমরা এটি অধ্যায় ৩-এ আরও বিস্তারিতভাবে আলোচনা করব, কিন্তু আপাতত, জেনে রাখুন যে এই বৈশিষ্ট্যটি প্রায়শই ব্যবহৃত হয় যখন আপনি একটি মানকে এক টাইপ থেকে অন্য টাইপে রূপান্তর করতে চান।

আমরা এই নতুন ভ্যারিয়েবলটিকে guess.trim().parse() এক্সপ্রেশনের সাথে বাইন্ড করি। এক্সপ্রেশনের guess মূল guess ভ্যারিয়েবলটিকে বোঝায় যা ইনপুটটিকে একটি স্ট্রিং হিসাবে ধারণ করেছিল। একটি String ইনস্ট্যান্সের trim মেথড শুরু এবং শেষের যেকোনো হোয়াইটস্পেস দূর করে দেবে, যা আমাদের স্ট্রিংটিকে একটি u32-তে রূপান্তর করার আগে করতে হবে, যা কেবল সংখ্যাসূচক ডেটা ধারণ করতে পারে। ব্যবহারকারীকে read_line সন্তুষ্ট করতে এবং তাদের অনুমান ইনপুট করতে enter চাপতে হবে, যা স্ট্রিংটিতে একটি নিউলাইন ক্যারেক্টার যোগ করে। উদাহরণস্বরূপ, যদি ব্যবহারকারী 5 টাইপ করে এবং enter চাপে, guess দেখতে এমন হয়: 5\n\n "নিউলাইন" প্রতিনিধিত্ব করে। (উইন্ডোজে, enter চাপলে একটি ক্যারেজ রিটার্ন এবং একটি নিউলাইন হয়, \r\n।) trim মেথড \n বা \r\n দূর করে, যার ফলে কেবল 5 থাকে।

স্ট্রিং-এর উপর parse মেথড একটি স্ট্রিংকে অন্য টাইপে রূপান্তর করে। এখানে, আমরা এটিকে একটি স্ট্রিং থেকে একটি সংখ্যায় রূপান্তর করতে ব্যবহার করি। আমাদের রাস্টকে let guess: u32 ব্যবহার করে ঠিক কোন সংখ্যা টাইপ আমরা চাই তা বলতে হবে। guess-এর পরে কোলন (:) রাস্টকে বলে যে আমরা ভ্যারিয়েবলের টাইপ অ্যানোটেট করব। রাস্টের কয়েকটি অন্তর্নির্মিত সংখ্যা টাইপ আছে; এখানে দেখা u32 একটি আনসাইন্ড, ৩২-বিট ইন্টিজার। এটি একটি ছোট ধনাত্মক সংখ্যার জন্য একটি ভাল ডিফল্ট পছন্দ। আপনি অধ্যায় ৩-এ অন্যান্য সংখ্যা টাইপ সম্পর্কে শিখবেন।

উপরন্তু, এই উদাহরণ প্রোগ্রামে u32 অ্যানোটেশন এবং secret_number-এর সাথে তুলনা করার মানে হল রাস্ট অনুমান করবে যে secret_number-ও একটি u32 হওয়া উচিত। তাই এখন তুলনাটি একই টাইপের দুটি মানের মধ্যে হবে!

parse মেথডটি কেবল সেই অক্ষরগুলির উপর কাজ করবে যেগুলিকে যৌক্তিকভাবে সংখ্যায় রূপান্তর করা যায় এবং তাই সহজেই ত্রুটি ঘটাতে পারে। উদাহরণস্বরূপ, যদি স্ট্রিংটিতে A👍% থাকে, তবে সেটিকে সংখ্যায় রূপান্তর করার কোনো উপায় থাকবে না। কারণ এটি ব্যর্থ হতে পারে, parse মেথডটি একটি Result টাইপ প্রদান করে, যেমনটি read_line মেথড করে (আগে “Handling Potential Failure with Result” বিভাগে আলোচনা করা হয়েছে)। আমরা এই Result-কে আবার expect মেথড ব্যবহার করে একইভাবে ব্যবহার করব। যদি parse একটি Err Result ভ্যারিয়েন্ট প্রদান করে কারণ এটি স্ট্রিং থেকে একটি সংখ্যা তৈরি করতে পারেনি, expect কলটি গেমটি ক্র্যাশ করাবে এবং আমরা যে বার্তাটি দিই তা প্রিন্ট করবে। যদি parse সফলভাবে স্ট্রিংটিকে একটি সংখ্যায় রূপান্তর করতে পারে, তবে এটি Result-এর Ok ভ্যারিয়েন্ট প্রদান করবে, এবং expect Ok মান থেকে আমরা যে সংখ্যাটি চাই তা প্রদান করবে।

চলুন এখন প্রোগ্রামটি চালাই:

$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.26s
     Running `target/debug/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
  76
You guessed: 76
Too big!

সুন্দর! অনুমানের আগে স্পেস যোগ করা সত্ত্বেও, প্রোগ্রামটি এখনও বুঝতে পেরেছে যে ব্যবহারকারী ৭৬ অনুমান করেছে। বিভিন্ন ধরণের ইনপুটের সাথে বিভিন্ন আচরণ যাচাই করার জন্য প্রোগ্রামটি কয়েকবার চালান: সংখ্যাটি সঠিকভাবে অনুমান করুন, খুব বেশি একটি সংখ্যা অনুমান করুন, এবং খুব কম একটি সংখ্যা অনুমান করুন।

আমাদের গেমের বেশিরভাগই এখন কাজ করছে, কিন্তু ব্যবহারকারী কেবল একটি অনুমান করতে পারে। চলুন একটি লুপ যোগ করে এটি পরিবর্তন করি!

লুপিংয়ের মাধ্যমে একাধিক অনুমানের অনুমতি দেওয়া

loop কীওয়ার্ড একটি অসীম লুপ তৈরি করে। আমরা ব্যবহারকারীদের সংখ্যাটি অনুমান করার আরও সুযোগ দিতে একটি লুপ যোগ করব:

ফাইলের নাম: src/main.rs

use std::cmp::Ordering;
use std::io;

use rand::Rng;

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

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

    // --snip--

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

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

        // --snip--


        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!"),
        }
    }
}

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

ব্যবহারকারী সর্বদা কীবোর্ড শর্টকাট ctrl-c ব্যবহার করে প্রোগ্রামটি বাধা দিতে পারে। কিন্তু এই অতৃপ্ত দৈত্য থেকে বাঁচার আরেকটি উপায় আছে, যেমনটি "Comparing the Guess to the Secret Number" বিভাগে parse আলোচনায় উল্লেখ করা হয়েছে: যদি ব্যবহারকারী একটি অ-সংখ্যা উত্তর প্রবেশ করায়, প্রোগ্রামটি ক্র্যাশ করবে। আমরা ব্যবহারকারীকে বের হওয়ার অনুমতি দিতে এর সুবিধা নিতে পারি, যেমনটি এখানে দেখানো হয়েছে:

$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.23s
     Running `target/debug/guessing_game`
Guess the number!
The secret number is: 59
Please input your guess.
45
You guessed: 45
Too small!
Please input your guess.
60
You guessed: 60
Too big!
Please input your guess.
59
You guessed: 59
You win!
Please input your guess.
quit

thread 'main' panicked at src/main.rs:28:47:
Please type a number!: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

quit টাইপ করলে গেমটি থেকে বের হয়ে যাবে, কিন্তু আপনি যেমন লক্ষ্য করবেন, অন্য যেকোনো অ-সংখ্যা ইনপুট প্রবেশ করালেও তাই হবে। এটি সর্বোত্তম থেকে অনেক দূরে; আমরা চাই যে সঠিক সংখ্যা অনুমান করা হলে গেমটি বন্ধ হয়ে যাক।

সঠিক অনুমানের পর খেলা শেষ করা

চলুন একটি break স্টেটমেন্ট যোগ করে গেমটি এমনভাবে প্রোগ্রাম করি যাতে ব্যবহারকারী জিতলে খেলাটি শেষ হয়ে যায়:

ফাইলের নাম: src/main.rs

use std::cmp::Ordering;
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}");

    loop {
        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}");

        // --snip--

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

You win! এর পরে break লাইনটি যোগ করলে ব্যবহারকারী যখন গোপন সংখ্যাটি সঠিকভাবে অনুমান করে তখন প্রোগ্রামটি লুপ থেকে বেরিয়ে যায়। লুপ থেকে বেরিয়ে যাওয়ার মানে হল প্রোগ্রাম থেকে বেরিয়ে যাওয়া, কারণ লুপটি main-এর শেষ অংশ।

অবৈধ ইনপুট পরিচালনা করা

গেমের আচরণ আরও পরিমার্জিত করার জন্য, ব্যবহারকারী যখন একটি অ-সংখ্যা ইনপুট করে তখন প্রোগ্রামটি ক্র্যাশ করার পরিবর্তে, চলুন গেমটিকে একটি অ-সংখ্যা উপেক্ষা করতে বাধ্য করি যাতে ব্যবহারকারী অনুমান চালিয়ে যেতে পারে। আমরা এটি সেই লাইনটি পরিবর্তন করে করতে পারি যেখানে guess একটি String থেকে একটি u32-তে রূপান্তরিত হয়, যেমনটি লিস্টিং ২-৫-এ দেখানো হয়েছে।

use std::cmp::Ordering;
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}");

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

        let mut guess = String::new();

        // --snip--

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

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

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

        // --snip--

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

আমরা একটি expect কল থেকে একটি match এক্সপ্রেশনে স্যুইচ করি যাতে একটি ত্রুটিতে ক্র্যাশ করার পরিবর্তে ত্রুটিটি পরিচালনা করা যায়। মনে রাখবেন যে parse একটি Result টাইপ প্রদান করে এবং Result একটি enum যার Ok এবং Err ভ্যারিয়েন্ট রয়েছে। আমরা এখানে একটি match এক্সপ্রেশন ব্যবহার করছি, যেমনটি আমরা cmp মেথডের Ordering ফলাফলের সাথে করেছিলাম।

যদি parse সফলভাবে স্ট্রিংটিকে একটি সংখ্যায় পরিণত করতে পারে, তবে এটি একটি Ok মান প্রদান করবে যা ফলস্বরূপ সংখ্যাটি ধারণ করে। সেই Ok মানটি প্রথম arm-এর প্যাটার্নের সাথে মিলবে, এবং match এক্সপ্রেশনটি কেবল parse দ্বারা উৎপাদিত এবং Ok মানের ভিতরে রাখা num মানটি প্রদান করবে। সেই সংখ্যাটি আমরা যে নতুন guess ভ্যারিয়েবলটি তৈরি করছি সেখানে ঠিক যেখানে আমরা চাই সেখানেই শেষ হবে।

যদি parse স্ট্রিংটিকে একটি সংখ্যায় পরিণত করতে না পারে, তবে এটি একটি Err মান প্রদান করবে যা ত্রুটি সম্পর্কে আরও তথ্য ধারণ করে। Err মানটি প্রথম match arm-এর Ok(num) প্যাটার্নের সাথে মেলে না, তবে এটি দ্বিতীয় arm-এর Err(_) প্যাটার্নের সাথে মেলে। আন্ডারস্কোর, _, একটি ক্যাচ-অল মান; এই উদাহরণে, আমরা বলছি যে আমরা সমস্ত Err মান মেলাতে চাই, তাদের ভিতরে যাই তথ্য থাকুক না কেন। তাই প্রোগ্রামটি দ্বিতীয় arm-এর কোড, continue, কার্যকর করবে, যা প্রোগ্রামকে loop-এর পরবর্তী পুনরাবৃত্তিতে যেতে এবং আরেকটি অনুমানের জন্য জিজ্ঞাসা করতে বলে। তাই, কার্যকরভাবে, প্রোগ্রামটি parse-এর সম্মুখীন হতে পারে এমন সমস্ত ত্রুটি উপেক্ষা করে!

এখন প্রোগ্রামের সবকিছু প্রত্যাশিতভাবে কাজ করা উচিত। চলুন চেষ্টা করি:

$ cargo run
   Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/guessing_game`
Guess the number!
The secret number is: 61
Please input your guess.
10
You guessed: 10
Too small!
Please input your guess.
99
You guessed: 99
Too big!
Please input your guess.
foo
Please input your guess.
61
You guessed: 61
You win!

অসাধারণ! একটি ছোট চূড়ান্ত পরিবর্তনের মাধ্যমে, আমরা guessing game শেষ করব। মনে রাখবেন যে প্রোগ্রামটি এখনও গোপন সংখ্যাটি প্রিন্ট করছে। এটি পরীক্ষার জন্য ভাল কাজ করেছে, কিন্তু এটি গেমটি নষ্ট করে দেয়। চলুন গোপন সংখ্যাটি আউটপুট করে এমন println! টি মুছে ফেলি। লিস্টিং ২-৬ চূড়ান্ত কোডটি দেখায়।

use std::cmp::Ordering;
use std::io;

use rand::Rng;

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

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

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

        let mut guess = String::new();

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

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

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

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

এই মুহূর্তে, আপনি সফলভাবে guessing game তৈরি করেছেন। অভিনন্দন!

সারাংশ

এই প্রজেক্টটি আপনাকে অনেক নতুন রাস্ট কনসেপ্টের সাথে পরিচিত করার একটি বাস্তবসম্মত উপায় ছিল: let, match, ফাংশন, এক্সটার্নাল crate-এর ব্যবহার, এবং আরও অনেক কিছু। পরবর্তী কয়েকটি অধ্যায়ে, আপনি এই ধারণাগুলি সম্পর্কে আরও বিস্তারিতভাবে শিখবেন। অধ্যায় ৩ এমন ধারণাগুলি কভার করে যা বেশিরভাগ প্রোগ্রামিং ভাষাতেই আছে, যেমন ভ্যারিয়েবল, ডেটা টাইপ, এবং ফাংশন, এবং দেখায় কিভাবে রাস্ট-এ সেগুলি ব্যবহার করতে হয়। অধ্যায় ৪ ওনারশিপ অন্বেষণ করে, একটি বৈশিষ্ট্য যা রাস্টকে অন্যান্য ভাষা থেকে আলাদা করে। অধ্যায় ৫ struct এবং মেথড সিনট্যাক্স নিয়ে আলোচনা করে, এবং অধ্যায় ৬ ব্যাখ্যা করে কিভাবে enum কাজ করে।