ফাংশন (Functions)
রাস্ট কোডে ফাংশনের ব্যাপক ব্যবহার দেখা যায়। আপনি ইতিমধ্যে ভাষার সবচেয়ে গুরুত্বপূর্ণ ফাংশনগুলোর মধ্যে একটি দেখেছেন: main ফাংশন, যা অনেক প্রোগ্রামের প্রবেশ বিন্দু (entry point)। আপনি fn কীওয়ার্ডও দেখেছেন, যা আপনাকে নতুন ফাংশন ঘোষণা করার সুযোগ দেয়।
রাস্ট কোড ফাংশন এবং ভ্যারিয়েবলের নামের জন্য প্রচলিত শৈলী হিসাবে স্নেক কেস (snake case) ব্যবহার করে, যেখানে সমস্ত অক্ষর ছোট হাতের হয় এবং শব্দগুলোকে আন্ডারস্কোর দিয়ে আলাদা করা হয়। এখানে একটি প্রোগ্রাম রয়েছে যাতে একটি উদাহরণ ফাংশন সংজ্ঞা রয়েছে:
ফাইলের নাম: src/main.rs
fn main() { println!("Hello, world!"); another_function(); } fn another_function() { println!("Another function."); }
আমরা রাস্ট-এ একটি ফাংশন সংজ্ঞায়িত করি fn লিখে, তারপরে ফাংশনের নাম এবং একজোড়া প্রথম বন্ধনী। কোঁকড়া বন্ধনী (curly brackets) কম্পাইলারকে বলে দেয় যে ফাংশন বডি কোথায় শুরু এবং শেষ হয়েছে।
আমরা আমাদের সংজ্ঞায়িত যেকোনো ফাংশনকে তার নাম এবং তারপরে একজোড়া প্রথম বন্ধনী লিখে কল করতে পারি। যেহেতু another_function প্রোগ্রামে সংজ্ঞায়িত করা হয়েছে, তাই এটি main ফাংশনের ভিতর থেকে কল করা যেতে পারে। লক্ষ্য করুন যে আমরা সোর্স কোডে main ফাংশনের পরে another_function সংজ্ঞায়িত করেছি; আমরা এটি আগেও সংজ্ঞায়িত করতে পারতাম। রাস্ট আপনার ফাংশনগুলো কোথায় সংজ্ঞায়িত করা হয়েছে তা নিয়ে চিন্তা করে না, কেবল এটি দেখে যে সেগুলো কলারের দ্বারা দেখা যায় এমন একটি স্কোপে সংজ্ঞায়িত করা হয়েছে কিনা।
চলুন ফাংশন সম্পর্কে আরও অন্বেষণ করতে functions নামে একটি নতুন বাইনারি প্রজেক্ট শুরু করি। src/main.rs ফাইলে another_function উদাহরণটি রাখুন এবং এটি চালান। আপনার নিম্নলিখিত আউটপুট দেখতে পাওয়া উচিত:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
Running `target/debug/functions`
Hello, world!
Another function.
লাইনগুলো main ফাংশনে যেভাবে প্রদর্শিত হয়েছে সেই ক্রমে কার্যকর হয়। প্রথমে "Hello, world!" বার্তাটি প্রিন্ট হয়, এবং তারপর another_function কল করা হয় এবং এর বার্তাটি প্রিন্ট হয়।
প্যারামিটার (Parameters)
আমরা ফাংশনগুলোকে প্যারামিটার (parameters) সহ সংজ্ঞায়িত করতে পারি, যা ফাংশনের সিগনেচারের অংশ হিসাবে বিশেষ ভ্যারিয়েবল। যখন একটি ফাংশনে প্যারামিটার থাকে, আপনি সেই প্যারামিটারগুলোর জন্য এটিকে নির্দিষ্ট মান (concrete values) সরবরাহ করতে পারেন। প্রযুক্তিগতভাবে, নির্দিষ্ট মানগুলোকে আর্গুমেন্ট (arguments) বলা হয়, কিন্তু সাধারণ কথোপকথনে, লোকেরা প্যারামিটার এবং আর্গুমেন্ট শব্দ দুটিকে ফাংশনের সংজ্ঞায় ভ্যারিয়েবল বা ফাংশন কল করার সময় পাস করা নির্দিষ্ট মানগুলোর জন্য अदলবদল করে ব্যবহার করে।
another_function-এর এই সংস্করণে আমরা একটি প্যারামিটার যোগ করি:
ফাইলের নাম: src/main.rs
fn main() { another_function(5); } fn another_function(x: i32) { println!("The value of x is: {x}"); }
এই প্রোগ্রামটি চালানোর চেষ্টা করুন; আপনার নিম্নলিখিত আউটপুট পাওয়া উচিত:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.21s
Running `target/debug/functions`
The value of x is: 5
another_function-এর ঘোষণায় x নামে একটি প্যারামিটার রয়েছে। x-এর টাইপ i32 হিসাবে নির্দিষ্ট করা হয়েছে। যখন আমরা another_function-এ 5 পাস করি, println! ম্যাক্রো ফরম্যাট স্ট্রিং-এ x ধারণকারী কোঁকড়া বন্ধনীর জোড়ার জায়গায় 5 রাখে।
ফাংশনের সিগনেচারে, আপনাকে অবশ্যই প্রতিটি প্যারামিটারের টাইপ ঘোষণা করতে হবে। এটি রাস্টের ডিজাইনের একটি ইচ্ছাকৃত সিদ্ধান্ত: ফাংশন সংজ্ঞায় টাইপ অ্যানোটেশন প্রয়োজন হওয়ায় কম্পাইলারকে প্রায় কখনোই কোডের অন্য কোথাও আপনার কী টাইপ বোঝাতে চাইছেন তা বের করতে আপনার সাহায্য নিতে হয় না। কম্পাইলার আরও সহায়ক এরর বার্তা দিতে সক্ষম হয় যদি সে জানে ফাংশনটি কোন টাইপ আশা করছে।
একাধিক প্যারামিটার সংজ্ঞায়িত করার সময়, প্যারামিটার ঘোষণাগুলোকে কমা দিয়ে আলাদা করুন, যেমন:
ফাইলের নাম: src/main.rs
fn main() { print_labeled_measurement(5, 'h'); } fn print_labeled_measurement(value: i32, unit_label: char) { println!("The measurement is: {value}{unit_label}"); }
এই উদাহরণটি print_labeled_measurement নামে একটি ফাংশন তৈরি করে যার দুটি প্যারামিটার রয়েছে। প্রথম প্যারামিটারের নাম value এবং এটি একটি i32। দ্বিতীয়টির নাম unit_label এবং এর টাইপ char। ফাংশনটি তারপর value এবং unit_label উভয়ই ধারণকারী টেক্সট প্রিন্ট করে।
চলুন এই কোডটি চালানোর চেষ্টা করি। আপনার functions প্রজেক্টের src/main.rs ফাইলের বর্তমান প্রোগ্রামটিকে পূর্ববর্তী উদাহরণ দিয়ে প্রতিস্থাপন করুন এবং cargo run ব্যবহার করে এটি চালান:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/functions`
The measurement is: 5h```
যেহেতু আমরা `value`-এর জন্য `5` এবং `unit_label`-এর জন্য `'h'` মান দিয়ে ফাংশনটি কল করেছি, তাই প্রোগ্রামের আউটপুটে সেই মানগুলো রয়েছে।
### স্টেটমেন্ট এবং এক্সপ্রেশন (Statements and Expressions)
ফাংশন বডি একটি সিরিজের স্টেটমেন্ট (statements) দিয়ে গঠিত যা ঐচ্ছিকভাবে একটি এক্সপ্রেশন (expression) দিয়ে শেষ হতে পারে। এখন পর্যন্ত, আমরা যে ফাংশনগুলো দেখেছি সেগুলিতে একটি সমাপ্তি এক্সপ্রেশন অন্তর্ভুক্ত ছিল না, তবে আপনি একটি স্টেটমেন্টের অংশ হিসাবে একটি এক্সপ্রেশন দেখেছেন। যেহেতু রাস্ট একটি এক্সপ্রেশন-ভিত্তিক ভাষা, তাই এটি বোঝা একটি গুরুত্বপূর্ণ পার্থক্য। অন্যান্য ভাষার একই পার্থক্য নেই, তাই চলুন দেখি স্টেটমেন্ট এবং এক্সপ্রেশন কী এবং তাদের পার্থক্য ফাংশনের বডিকে কীভাবে প্রভাবিত করে।
- স্টেটমেন্ট হলো এমন নির্দেশ যা কোনো কাজ সম্পাদন করে এবং কোনো মান রিটার্ন করে না।
- এক্সপ্রেশন একটি ফলস্বরূপ মানে (resultant value) রূপান্তরিত হয়।
চলুন কিছু উদাহরণ দেখি।
আমরা আসলে ইতিমধ্যে স্টেটমেন্ট এবং এক্সপ্রেশন ব্যবহার করেছি। `let` কীওয়ার্ড দিয়ে একটি ভ্যারিয়েবল তৈরি করা এবং তাতে একটি মান অ্যাসাইন করা একটি স্টেটমেন্ট। লিস্টিং ৩-১-এ, `let y = 6;` একটি স্টেটমেন্ট।
<Listing number="3-1" file-name="src/main.rs" caption="একটি স্টেটমেন্ট ধারণকারী `main` ফাংশনের ঘোষণা">
```rust
fn main() {
let y = 6;
}
ফাংশন সংজ্ঞাও স্টেটমেন্ট; পুরো পূর্ববর্তী উদাহরণটি নিজেই একটি স্টেটমেন্ট। (যেমন আমরা নিচে দেখব, একটি ফাংশন কল করা একটি স্টেটমেন্ট নয়, যদিও।)
স্টেটমেন্ট মান রিটার্ন করে না। অতএব, আপনি একটি let স্টেটমেন্টকে অন্য ভ্যারিয়েবলে অ্যাসাইন করতে পারবেন না, যেমন নিম্নলিখিত কোডটি করার চেষ্টা করে; আপনি একটি এরর পাবেন:
ফাইলের নাম: src/main.rs
fn main() {
let x = (let y = 6);
}
যখন আপনি এই প্রোগ্রামটি চালান, তখন আপনি যে এররটি পাবেন তা এইরকম দেখায়:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found `let` statement
--> src/main.rs:2:14
|
2 | let x = (let y = 6);
| ^^^
|
= note: only supported directly in conditions of `if` and `while` expressions
warning: unnecessary parentheses around assigned value
--> src/main.rs:2:13
|
2 | let x = (let y = 6);
| ^ ^
|
= note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
|
2 - let x = (let y = 6);
2 + let x = let y = 6;
|
warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` (bin "functions") due to 1 previous error; 1 warning emitted
let y = 6 স্টেটমেন্টটি কোনো মান রিটার্ন করে না, তাই x-এর বাইন্ড করার জন্য কিছু নেই। এটি অন্যান্য ভাষায় যা ঘটে তার থেকে ভিন্ন, যেমন C এবং Ruby, যেখানে অ্যাসাইনমেন্টটি অ্যাসাইনমেন্টের মান রিটার্ন করে। সেই ভাষাগুলিতে, আপনি x = y = 6 লিখতে পারেন এবং x এবং y উভয়েরই মান 6 হতে পারে; রাস্ট-এ তা হয় না।
এক্সপ্রেশন একটি মানে রূপান্তরিত হয় এবং আপনি রাস্ট-এ যে বাকি কোড লিখবেন তার বেশিরভাগই তৈরি করে। একটি গণিত অপারেশন বিবেচনা করুন, যেমন 5 + 6, যা একটি এক্সপ্রেশন যা 11 মানে রূপান্তরিত হয়। এক্সপ্রেশন স্টেটমেন্টের অংশ হতে পারে: লিস্টিং ৩-১-এ, let y = 6; স্টেটমেন্টের 6 একটি এক্সপ্রেশন যা 6 মানে রূপান্তরিত হয়। একটি ফাংশন কল করা একটি এক্সপ্রেশন। একটি ম্যাক্রো কল করা একটি এক্সপ্রেশন। কোঁকড়া বন্ধনী দিয়ে তৈরি একটি নতুন স্কোপ ব্লক একটি এক্সপ্রেশন, উদাহরণস্বরূপ:
ফাইলের নাম: src/main.rs
fn main() { let y = { let x = 3; x + 1 }; println!("The value of y is: {y}"); }
এই এক্সপ্রেশনটি:
{
let x = 3;
x + 1
}
একটি ব্লক যা, এই ক্ষেত্রে, 4-এ রূপান্তরিত হয়। সেই মানটি let স্টেটমেন্টের অংশ হিসাবে y-এর সাথে বাইন্ড করা হয়। লক্ষ্য করুন যে x + 1 লাইনের শেষে একটি সেমিকোলন নেই, যা আপনি এখন পর্যন্ত দেখা বেশিরভাগ লাইনের থেকে ভিন্ন। এক্সপ্রেশনের শেষে সেমিকোলন থাকে না। যদি আপনি একটি এক্সপ্রেশনের শেষে সেমিকোলন যোগ করেন, তবে আপনি এটিকে একটি স্টেটমেন্টে পরিণত করেন, এবং এটি তখন কোনো মান রিটার্ন করবে না। ফাংশন রিটার্ন ভ্যালু এবং এক্সপ্রেশন অন্বেষণ করার সময় এটি মনে রাখবেন।
রিটার্ন ভ্যালু সহ ফাংশন (Functions with Return Values)
ফাংশনগুলো কল করা কোডে মান রিটার্ন করতে পারে। আমরা রিটার্ন ভ্যালুর নাম দিই না, তবে আমাদের অবশ্যই একটি তীরচিহ্ন (->) এর পরে তাদের টাইপ ঘোষণা করতে হবে। রাস্ট-এ, ফাংশনের রিটার্ন ভ্যালু ফাংশনের বডির ব্লকের চূড়ান্ত এক্সপ্রেশনের মানের সমার্থক। আপনি return কীওয়ার্ড ব্যবহার করে এবং একটি মান নির্দিষ্ট করে একটি ফাংশন থেকে আগেভাগে রিটার্ন করতে পারেন, তবে বেশিরভাগ ফাংশন শেষ এক্সপ্রেশনটি অন্তর্নিহিতভাবে রিটার্ন করে। এখানে একটি ফাংশনের উদাহরণ দেওয়া হল যা একটি মান রিটার্ন করে:
ফাইলের নাম: src/main.rs
fn five() -> i32 { 5 } fn main() { let x = five(); println!("The value of x is: {x}"); }
five ফাংশনে কোনো ফাংশন কল, ম্যাক্রো, বা এমনকি let স্টেটমেন্টও নেই—শুধু 5 সংখ্যাটি নিজেই। এটি রাস্ট-এ একটি সম্পূর্ণ বৈধ ফাংশন। লক্ষ্য করুন যে ফাংশনের রিটার্ন টাইপও -> i32 হিসাবে নির্দিষ্ট করা হয়েছে। এই কোডটি চালানোর চেষ্টা করুন; আউটপুটটি এইরকম হওয়া উচিত:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/functions`
The value of x is: 5
five-এর 5 হল ফাংশনের রিটার্ন ভ্যালু, যার কারণে রিটার্ন টাইপ i32। চলুন এটি আরও বিস্তারিতভাবে পরীক্ষা করি। দুটি গুরুত্বপূর্ণ বিষয় রয়েছে: প্রথমত, let x = five(); লাইনটি দেখায় যে আমরা একটি ফাংশনের রিটার্ন ভ্যালু ব্যবহার করে একটি ভ্যারিয়েবল ইনিশিয়ালাইজ করছি। যেহেতু five ফাংশনটি একটি 5 রিটার্ন করে, তাই সেই লাইনটি নিম্নলিখিতটির সমান:
#![allow(unused)] fn main() { let x = 5; }
দ্বিতীয়ত, five ফাংশনের কোনো প্যারামিটার নেই এবং রিটার্ন ভ্যালুর টাইপ সংজ্ঞায়িত করে, কিন্তু ফাংশনের বডিটি একটি একাকী 5 যার কোনো সেমিকোলন নেই কারণ এটি একটি এক্সপ্রেশন যার মান আমরা রিটার্ন করতে চাই।
চলুন আরেকটি উদাহরণ দেখি:
ফাইলের নাম: src/main.rs
fn main() { let x = plus_one(5); println!("The value of x is: {x}"); } fn plus_one(x: i32) -> i32 { x + 1 }
এই কোডটি চালালে The value of x is: 6 প্রিন্ট হবে। কিন্তু যদি আমরা x + 1 ধারণকারী লাইনের শেষে একটি সেমিকোলন রাখি, এটিকে একটি এক্সপ্রেশন থেকে একটি স্টেটমেন্টে পরিবর্তন করে, আমরা একটি এরর পাব:
ফাইলের নাম: src/main.rs
fn main() {
let x = plus_one(5);
println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
x + 1;
}
এই কোডটি কম্পাইল করলে একটি এরর উৎপন্ন হয়, যেমন:
$ cargo run
Compiling functions v0.1.0 (file:///projects/functions)
error[E0308]: mismatched types
--> src/main.rs:7:24
|
7 | fn plus_one(x: i32) -> i32 {
| -------- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
8 | x + 1;
| - help: remove this semicolon to return this value
For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` (bin "functions") due to 1 previous error
প্রধান এরর বার্তা, mismatched types (বেমানান টাইপ), এই কোডের মূল সমস্যাটি প্রকাশ করে। plus_one ফাংশনের সংজ্ঞা বলছে যে এটি একটি i32 রিটার্ন করবে, কিন্তু স্টেটমেন্টগুলো কোনো মানে রূপান্তরিত হয় না, যা () (ইউনিট টাইপ) দ্বারা প্রকাশ করা হয়। অতএব, কিছুই রিটার্ন করা হয় না, যা ফাংশন সংজ্ঞার সাথে বিরোধিতা করে এবং একটি এররের কারণ হয়। এই আউটপুটে, রাস্ট সম্ভবত এই সমস্যাটি সংশোধন করতে সাহায্য করার জন্য একটি বার্তা প্রদান করে: এটি সেমিকোলনটি সরানোর পরামর্শ দেয়, যা এররটি ঠিক করবে।