ফাংশন (Functions)
Rust কোডে ফাংশন সর্বত্র বিদ্যমান। আপনি ইতিমধ্যেই ভাষার সবচেয়ে গুরুত্বপূর্ণ ফাংশনগুলোর মধ্যে একটি দেখেছেন: main
ফাংশন, যেটি অনেক প্রোগ্রামের এন্ট্রি পয়েন্ট। আপনি fn
কীওয়ার্ডটিও দেখেছেন, যেটি আপনাকে নতুন ফাংশন ঘোষণা করতে দেয়।
Rust কোড ফাংশন এবং ভেরিয়েবলের নামের জন্য প্রচলিত স্টাইল হিসাবে স্নেক কেস (snake case) ব্যবহার করে, যেখানে সমস্ত অক্ষর ছোট হাতের হয় এবং শব্দগুলো আন্ডারস্কোর দিয়ে আলাদা করা হয়। এখানে একটি প্রোগ্রাম রয়েছে যাতে একটি উদাহরণ ফাংশন সংজ্ঞা রয়েছে:
Filename: src/main.rs
fn main() { println!("Hello, world!"); another_function(); } fn another_function() { println!("Another function."); }
আমরা Rust-এ একটি ফাংশন সংজ্ঞায়িত করি fn
লিখে, তারপর একটি ফাংশনের নাম এবং এক সেট প্যারেন্থেসিস দিয়ে। কার্লি ব্র্যাকেটগুলো কম্পাইলারকে বলে যে ফাংশন বডি কোথায় শুরু এবং শেষ হয়।
আমরা যে কোনো ফাংশনকে তার নাম লিখে এবং তারপর এক সেট প্যারেন্থেসিস দিয়ে কল করতে পারি। যেহেতু another_function
প্রোগ্রামটিতে সংজ্ঞায়িত করা হয়েছে, তাই এটিকে main
ফাংশনের ভিতর থেকে কল করা যেতে পারে। লক্ষ্য করুন যে আমরা সোর্স কোডে main
ফাংশনের পরে another_function
সংজ্ঞায়িত করেছি; আমরা এটিকে আগেও সংজ্ঞায়িত করতে পারতাম। Rust-এ আপনি কোথায় আপনার ফাংশনগুলো সংজ্ঞায়িত করছেন সেটি গুরুত্বপূর্ণ নয়, শুধুমাত্র সেগুলোকে এমন কোথাও সংজ্ঞায়িত করা দরকার যা কলার (caller) দেখতে পায়।
ফাংশনগুলো আরও বিশদে জানতে চলুন 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)
আমরা ফাংশনগুলোকে প্যারামিটার রাখার জন্য সংজ্ঞায়িত করতে পারি, যেগুলো হল বিশেষ ভেরিয়েবল যা একটি ফাংশনের সিগনেচারের অংশ। যখন একটি ফাংশনের প্যারামিটার থাকে, তখন আপনি সেগুলোকে সেই প্যারামিটারগুলোর জন্য নির্দিষ্ট মান সরবরাহ করতে পারেন। টেকনিক্যালি, নির্দিষ্ট মানগুলোকে আর্গুমেন্ট (arguments) বলা হয়, কিন্তু সাধারণ কথোপকথনে, লোকেরা ফাংশনের সংজ্ঞার ভেরিয়েবল বা ফাংশন কল করার সময় দেওয়া নির্দিষ্ট মান, উভয়ের জন্যই প্যারামিটার এবং আর্গুমেন্ট শব্দগুলো ব্যবহার করে।
another_function
-এর এই ভার্সনে আমরা একটি প্যারামিটার যোগ করি:
Filename: 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
বসিয়ে দেয়।
ফাংশন সিগনেচারে, আপনাকে অবশ্যই প্রতিটি প্যারামিটারের টাইপ ঘোষণা করতে হবে। এটি Rust-এর ডিজাইনের একটি ইচ্ছাকৃত সিদ্ধান্ত: ফাংশন সংজ্ঞায় টাইপ অ্যানোটেশন প্রয়োজন হওয়ার অর্থ হল কম্পাইলারকে আপনার বোঝানো টাইপটি বের করার জন্য কোডের অন্য কোথাও সেগুলো ব্যবহার করার প্রয়োজন প্রায় কখনই হয় না। কম্পাইলার আরও সহায়ক এরর মেসেজ দিতে সক্ষম হয় যদি এটি জানে যে ফাংশনটি কোন টাইপ আশা করে।
একাধিক প্যারামিটার সংজ্ঞায়িত করার সময়, প্যারামিটার ঘোষণাগুলোকে কমা দিয়ে আলাদা করুন, এইভাবে:
Filename: 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) দিয়ে শেষ হতে পারে। ఇప్పటి পর্যন্ত, আমরা যেসব ফাংশন কভার করেছি, সেগুলোতে কোনো শেষ এক্সপ্রেশন ছিল না, কিন্তু আপনি একটি স্টেটমেন্টের অংশ হিসাবে একটি এক্সপ্রেশন দেখেছেন। যেহেতু Rust একটি এক্সপ্রেশন-ভিত্তিক ভাষা, তাই এটি একটি গুরুত্বপূর্ণ পার্থক্য যা বোঝা দরকার। অন্যান্য ভাষার একই পার্থক্য নেই, তাই দেখা যাক স্টেটমেন্ট এবং এক্সপ্রেশন কী এবং তাদের পার্থক্যগুলো ফাংশনের বডিগুলোকে কীভাবে প্রভাবিত করে।
- স্টেটমেন্ট হল নির্দেশাবলী যা কিছু কাজ সম্পাদন করে এবং কোনো মান রিটার্ন করে না।
- এক্সপ্রেশন একটি ফলাফলের মান মূল্যায়ন করে। চলুন কিছু উদাহরণ দেখি।
আমরা আসলে ইতিমধ্যেই স্টেটমেন্ট এবং এক্সপ্রেশন ব্যবহার করেছি। let
কীওয়ার্ড দিয়ে একটি ভেরিয়েবল তৈরি করা এবং এতে একটি মান অ্যাসাইন করা হল একটি স্টেটমেন্ট। Listing 3-1-এ, let y = 6;
হল একটি স্টেটমেন্ট।
fn main() { let y = 6; }
ফাংশন ডেফিনিশনগুলোও স্টেটমেন্ট; উপরের সম্পূর্ণ উদাহরণটি নিজেই একটি স্টেটমেন্ট। (আমরা নিচে দেখতে পাব, একটি ফাংশনকে কল করা স্টেটমেন্ট নয়।)
স্টেটমেন্টগুলো মান রিটার্ন করে না। অতএব, আপনি অন্য ভেরিয়েবলে একটি let
স্টেটমেন্ট অ্যাসাইন করতে পারবেন না, যেমনটি নিম্নলিখিত কোড করার চেষ্টা করে; আপনি একটি এরর পাবেন:
Filename: 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
হতে পারে; Rust-এর ক্ষেত্রে তা হয় না।
এক্সপ্রেশনগুলো একটি মান মূল্যায়ন করে এবং আপনি Rust-এ লিখবেন এমন বেশিরভাগ কোড তৈরি করে। একটি গাণিতিক অপারেশন বিবেচনা করুন, যেমন 5 + 6
, যেটি একটি এক্সপ্রেশন যা 11
মানে মূল্যায়ন করে। এক্সপ্রেশনগুলো স্টেটমেন্টের অংশ হতে পারে: Listing 3-1-এ, let y = 6;
স্টেটমেন্টের 6
হল একটি এক্সপ্রেশন যা 6
মানে মূল্যায়ন করে। একটি ফাংশন কল করা একটি এক্সপ্রেশন। একটি ম্যাক্রো কল করা একটি এক্সপ্রেশন। কার্লি ব্র্যাকেট দিয়ে তৈরি একটি নতুন স্কোপ ব্লক হল একটি এক্সপ্রেশন, উদাহরণস্বরূপ:
Filename: 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)
ফাংশনগুলো সেই কোডে মান রিটার্ন করতে পারে যেখান থেকে সেগুলোকে কল করা হয়েছে। আমরা রিটার্ন ভ্যালুগুলোর নাম দিই না, তবে একটি তীর চিহ্নের (->
) পরে আমাদের তাদের টাইপ ঘোষণা করতে হবে। Rust-এ, ফাংশনের রিটার্ন ভ্যালু, ফাংশনের বডির ব্লকের ফাইনাল এক্সপ্রেশনের মানের সমার্থক। আপনি return
কীওয়ার্ড ব্যবহার করে এবং একটি মান নির্দিষ্ট করে একটি ফাংশন থেকে তাড়াতাড়ি রিটার্ন করতে পারেন, তবে বেশিরভাগ ফাংশন শেষ এক্সপ্রেশনটিকেই রিটার্ন করে। এখানে একটি ফাংশনের উদাহরণ দেওয়া হল যা একটি মান রিটার্ন করে:
Filename: src/main.rs
fn five() -> i32 { 5 } fn main() { let x = five(); println!("The value of x is: {x}"); }
five
ফাংশনে কোনো ফাংশন কল, ম্যাক্রো বা এমনকি let
স্টেটমেন্টও নেই—শুধু সংখ্যা 5
নিজে। Rust-এ এটি একটি সম্পূর্ণ বৈধ ফাংশন। মনে রাখবেন যে ফাংশনের রিটার্ন টাইপও নির্দিষ্ট করা হয়েছে, -> 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
যেখানে কোনো সেমিকোলন নেই, কারণ এটি একটি এক্সপ্রেশন যার মান আমরা রিটার্ন করতে চাই।
চলুন আরেকটি উদাহরণ দেখি:
Filename: 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
সম্বলিত লাইনের শেষে একটি সেমিকোলন বসিয়ে দিই, এটিকে একটি এক্সপ্রেশন থেকে একটি স্টেটমেন্টে পরিবর্তন করি, তাহলে আমরা একটি এরর পাব:
Filename: 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
রিটার্ন করবে, কিন্তু স্টেটমেন্টগুলো কোনো মান মূল্যায়ন করে না, যেটি ()
, ইউনিট টাইপ দ্বারা প্রকাশ করা হয়। অতএব, কিছুই রিটার্ন করা হয়নি, যা ফাংশনের সংজ্ঞার বিপরীত এবং এর ফলে একটি এরর হয়। এই আউটপুটে, Rust সম্ভবত এই সমস্যাটি সংশোধন করতে সাহায্য করার জন্য একটি মেসেজ প্রদান করে: এটি সেমিকোলনটি সরানোর পরামর্শ দেয়, যা এররটি ঠিক করবে।