স্ট্রাকট সংজ্ঞায়িত এবং ইন্সট্যানশিয়েট করা (Defining and Instantiating Structs)

স্ট্রাকটগুলো টাপলগুলোর মতোই, যা “টাপল টাইপ” বিভাগে আলোচিত হয়েছে। উভয়ই একাধিক সম্পর্কিত মান ধারণ করে। টাপলগুলোর মতো, একটি স্ট্রাকটের অংশগুলো বিভিন্ন টাইপের হতে পারে। টাপলগুলোর বিপরীতে, একটি স্ট্রাকটে আপনি ডেটার প্রতিটি অংশের নাম দেবেন যাতে মানগুলোর অর্থ স্পষ্ট হয়। এই নামগুলো যুক্ত করার অর্থ হল স্ট্রাকটগুলো টাপলগুলোর চেয়ে বেশি নমনীয়: একটি ইন্সট্যান্সের মান নির্দিষ্ট করতে বা অ্যাক্সেস করতে আপনাকে ডেটার ক্রমের উপর নির্ভর করতে হবে না।

একটি স্ট্রাকট সংজ্ঞায়িত করতে, আমরা struct কীওয়ার্ডটি লিখি এবং পুরো স্ট্রাকটটির নাম দিই। একটি স্ট্রাকটের নাম ডেটার অংশগুলোকে একত্রিত করার তাৎপর্য বর্ণনা করবে। তারপর, কার্লি ব্র্যাকেটের ভিতরে, আমরা ডেটার অংশগুলোর নাম এবং টাইপ সংজ্ঞায়িত করি, যাকে আমরা ফিল্ড (fields) বলি। উদাহরণস্বরূপ, Listing 5-1 একটি ব্যবহারকারী অ্যাকাউন্ট সম্পর্কে তথ্য সংরক্ষণ করে এমন একটি স্ট্রাকট দেখায়।

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {}

একটি স্ট্রাকট সংজ্ঞায়িত করার পরে সেটি ব্যবহার করতে, আমরা প্রতিটি ফিল্ডের জন্য নির্দিষ্ট মান উল্লেখ করে সেই স্ট্রাকটের একটি ইন্সট্যান্স (instance) তৈরি করি। আমরা স্ট্রাকটের নাম উল্লেখ করে এবং তারপর কী (key): মান (value) জোড়া ধারণকারী কার্লি ব্র্যাকেট যুক্ত করে একটি ইন্সট্যান্স তৈরি করি, যেখানে কীগুলো হল ফিল্ডগুলোর নাম এবং মানগুলো হল সেই ফিল্ডগুলোতে আমরা যে ডেটা সংরক্ষণ করতে চাই। আমাদেরকে স্ট্রাকটে যে ক্রমে ফিল্ডগুলো ঘোষণা করেছি সেই একই ক্রমে নির্দিষ্ট করতে হবে না। অন্য কথায়, স্ট্রাকট সংজ্ঞাটি টাইপের জন্য একটি সাধারণ টেমপ্লেটের মতো এবং ইন্সট্যান্সগুলো সেই টেমপ্লেটটিকে নির্দিষ্ট ডেটা দিয়ে পূরণ করে টাইপের মান তৈরি করে। উদাহরণস্বরূপ, আমরা Listing 5-2-তে দেখানো একটি নির্দিষ্ট ব্যবহারকারী ঘোষণা করতে পারি।

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };
}

একটি স্ট্রাকট থেকে একটি নির্দিষ্ট মান পেতে, আমরা ডট নোটেশন ব্যবহার করি। উদাহরণস্বরূপ, এই ব্যবহারকারীর ইমেল ঠিকানা অ্যাক্সেস করতে, আমরা user1.email ব্যবহার করি। যদি ইন্সট্যান্সটি মিউটেবল হয়, তাহলে আমরা ডট নোটেশন ব্যবহার করে এবং একটি নির্দিষ্ট ফিল্ডে অ্যাসাইন করে একটি মান পরিবর্তন করতে পারি। Listing 5-3 একটি মিউটেবল User ইন্সট্যান্সের email ফিল্ডের মান কীভাবে পরিবর্তন করতে হয় তা দেখায়।

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let mut user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };

    user1.email = String::from("anotheremail@example.com");
}

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

Listing 5-4 একটি build_user ফাংশন দেখায় যা প্রদত্ত ইমেল এবং ব্যবহারকারীর নাম সহ একটি User ইন্সট্যান্স রিটার্ন করে। active ফিল্ডটি true মান পায় এবং sign_in_count 1 মান পায়।

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username: username,
        email: email,
        sign_in_count: 1,
    }
}

fn main() {
    let user1 = build_user(
        String::from("someone@example.com"),
        String::from("someusername123"),
    );
}

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

ফিল্ড ইনিট শর্টহ্যান্ড ব্যবহার করা (Using the Field Init Shorthand)

যেহেতু Listing 5-4-এ প্যারামিটারের নাম এবং স্ট্রাকট ফিল্ডের নামগুলো হুবহু একই, তাই আমরা ফিল্ড ইনিট শর্টহ্যান্ড (field init shorthand) সিনট্যাক্স ব্যবহার করে build_user পুনরায় লিখতে পারি যাতে এটি একই আচরণ করে কিন্তু username এবং email-এর পুনরাবৃত্তি না থাকে, যেমনটি Listing 5-5-এ দেখানো হয়েছে।

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username,
        email,
        sign_in_count: 1,
    }
}

fn main() {
    let user1 = build_user(
        String::from("someone@example.com"),
        String::from("someusername123"),
    );
}

এখানে, আমরা User স্ট্রাকটের একটি নতুন ইন্সট্যান্স তৈরি করছি, যার একটি ফিল্ডের নাম email। আমরা email ফিল্ডের মান build_user ফাংশনের email প্যারামিটারের মানটিতে সেট করতে চাই। যেহেতু email ফিল্ড এবং email প্যারামিটারের নাম একই, তাই আমাদের email: email-এর পরিবর্তে শুধুমাত্র email লিখতে হবে।

স্ট্রাকট আপডেট সিনট্যাক্স সহ অন্যান্য ইন্সট্যান্স থেকে ইন্সট্যান্স তৈরি করা (Creating Instances from Other Instances with Struct Update Syntax)

প্রায়শই একটি স্ট্রাকটের একটি নতুন ইন্সট্যান্স তৈরি করা দরকারী যা অন্য ইন্সট্যান্সের বেশিরভাগ মান অন্তর্ভুক্ত করে, কিন্তু কিছু পরিবর্তন করে। আপনি এটি স্ট্রাকট আপডেট সিনট্যাক্স (struct update syntax) ব্যবহার করে করতে পারেন।

প্রথমে, Listing 5-6-এ আমরা দেখাই কিভাবে user2-তে একটি নতুন User ইন্সট্যান্স নিয়মিতভাবে তৈরি করা যায়, আপডেট সিনট্যাক্স ছাড়া। আমরা email-এর জন্য একটি নতুন মান সেট করি কিন্তু অন্যথায় Listing 5-2-তে তৈরি করা user1 থেকে একই মান ব্যবহার করি।

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    // --snip--

    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };
}

স্ট্রাকট আপডেট সিনট্যাক্স ব্যবহার করে, আমরা কম কোড দিয়ে একই প্রভাব অর্জন করতে পারি, যেমনটি Listing 5-7-এ দেখানো হয়েছে। সিনট্যাক্স .. নির্দিষ্ট করে যে অবশিষ্ট ফিল্ডগুলো যেগুলো স্পষ্টভাবে সেট করা হয়নি সেগুলোর মান প্রদত্ত ইন্সট্যান্সের ফিল্ডগুলোর মতোই হওয়া উচিত।

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    // --snip--

    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}

Listing 5-7-এর কোডটিও user2-তে একটি ইন্সট্যান্স তৈরি করে যার email-এর জন্য একটি ভিন্ন মান রয়েছে কিন্তু user1 থেকে username, active এবং sign_in_count ফিল্ডগুলোর জন্য একই মান রয়েছে। ..user1 অবশ্যই শেষে আসতে হবে যাতে এটি নির্দিষ্ট করা যায় যে অবশিষ্ট ক্ষেত্রগুলির মান user1-এর সংশ্লিষ্ট ক্ষেত্রগুলি থেকে পাওয়া উচিত, তবে আমরা স্ট্রাকটের সংজ্ঞায় ক্ষেত্রগুলির ক্রম নির্বিশেষে, যে কোনও ক্রমে যতগুলি ক্ষেত্রের জন্য মান নির্দিষ্ট করতে পারি।

লক্ষ্য করুন যে স্ট্রাকট আপডেট সিনট্যাক্স একটি অ্যাসাইনমেন্টের মতো = ব্যবহার করে; এর কারণ হল এটি ডেটা মুভ করে, যেমনটি আমরা “ভেরিয়েবল এবং ডেটার মধ্যে মিথস্ক্রিয়া: মুভ” বিভাগে দেখেছি। এই উদাহরণে, user2 তৈরি করার পরে আমরা আর user1 ব্যবহার করতে পারি না কারণ user1-এর username ফিল্ডের String user2-তে সরানো হয়েছে। যদি আমরা email এবং username উভয়ের জন্য user2-কে নতুন String মান দিতাম এবং এইভাবে শুধুমাত্র user1 থেকে active এবং sign_in_count মান ব্যবহার করতাম, তাহলে user2 তৈরি করার পরেও user1 বৈধ থাকত। active এবং sign_in_count উভয়ই এমন টাইপ যা Copy ট্রেইট ইমপ্লিমেন্ট করে, তাই “শুধুমাত্র স্ট্যাক-ডেটা: কপি” বিভাগে আমরা যে আচরণ নিয়ে আলোচনা করেছি তা প্রযোজ্য হবে। এই উদাহরণে আমরা এখনও user1.email ব্যবহার করতে পারি, কারণ এর মান সরানো হয়নি

নামযুক্ত ক্ষেত্র ছাড়া টাপল স্ট্রাকট ব্যবহার করে ভিন্ন টাইপ তৈরি করা (Using Tuple Structs Without Named Fields to Create Different Types)

Rust টাপলের মতো দেখতে স্ট্রাকটগুলোকেও সমর্থন করে, যাকে টাপল স্ট্রাকট (tuple structs) বলা হয়। টাপল স্ট্রাকটগুলোর অতিরিক্ত অর্থ রয়েছে যা স্ট্রাকটের নাম সরবরাহ করে কিন্তু তাদের ক্ষেত্রগুলোর সাথে যুক্ত নাম নেই; বরং, তাদের কেবল ক্ষেত্রগুলোর টাইপ রয়েছে। টাপল স্ট্রাকটগুলো দরকারী যখন আপনি পুরো টাপলটিকে একটি নাম দিতে চান এবং টাপলটিকে অন্যান্য টাপল থেকে আলাদা টাইপ করতে চান এবং যখন প্রতিটি ক্ষেত্রের নামকরণ করা নিয়মিত স্ট্রাকটের মতো শব্দবহুল বা অপ্রয়োজনীয় হবে।

একটি টাপল স্ট্রাকট সংজ্ঞায়িত করতে, struct কীওয়ার্ড এবং স্ট্রাকটের নাম দিয়ে শুরু করুন এবং তারপরে টাপলের টাইপগুলো দিন। উদাহরণস্বরূপ, এখানে আমরা Color এবং Point নামে দুটি টাপল স্ট্রাকট সংজ্ঞায়িত এবং ব্যবহার করি:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

লক্ষ্য করুন যে black এবং origin মানগুলো ভিন্ন টাইপের, কারণ সেগুলো ভিন্ন টাপল স্ট্রাকটের ইন্সট্যান্স। আপনি সংজ্ঞায়িত করা প্রতিটি স্ট্রাকট তার নিজস্ব টাইপ, যদিও স্ট্রাকটের ভেতরের ক্ষেত্রগুলোর একই টাইপ থাকতে পারে। উদাহরণস্বরূপ, Color টাইপের একটি প্যারামিটার নেয় এমন একটি ফাংশন Point-কে আর্গুমেন্ট হিসাবে নিতে পারে না, যদিও উভয় টাইপ তিনটি i32 মান দিয়ে তৈরি। অন্যথায়, টাপল স্ট্রাকট ইন্সট্যান্সগুলো টাপলের মতোই, যেখানে আপনি সেগুলোকে তাদের পৃথক অংশে ডিস্ট্রাকচার করতে পারেন এবং আপনি একটি . ব্যবহার করতে পারেন। ইনডেক্স দ্বারা একটি পৃথক মান অ্যাক্সেস করতে পারেন। টাপলের মতন, টাপল স্ট্রাকটগুলিকে ডিস্ট্রাকচার করার সময় আপনাকে স্ট্রাকটের টাইপের নাম দিতে হবে। উদাহরণস্বরূপ, আমরা let Point(x, y, z) = point লিখব।

কোনো ক্ষেত্র ছাড়া ইউনিট-সদৃশ স্ট্রাকট (Unit-Like Structs Without Any Fields)

আপনি এমন স্ট্রাকটও সংজ্ঞায়িত করতে পারেন যেগুলোর কোনো ক্ষেত্র নেই! এগুলোকে ইউনিট-সদৃশ স্ট্রাকট (unit-like structs) বলা হয় কারণ সেগুলো ()-এর মতোই আচরণ করে, ইউনিট টাইপ যা আমরা “টাপল টাইপ” বিভাগে উল্লেখ করেছি। ইউনিট-সদৃশ স্ট্রাকটগুলো কার্যকর হতে পারে যখন আপনাকে কোনো টাইপের উপর একটি ট্রেইট ইমপ্লিমেন্ট করতে হবে কিন্তু টাইপের মধ্যে নিজে কোনো ডেটা সংরক্ষণ করতে চান না। আমরা চ্যাপ্টার ১০-এ ট্রেইট নিয়ে আলোচনা করব। এখানে AlwaysEqual নামক একটি ইউনিট স্ট্রাকট ঘোষণা এবং ইন্সট্যানশিয়েট করার একটি উদাহরণ দেওয়া হল:

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

AlwaysEqual সংজ্ঞায়িত করতে, আমরা struct কীওয়ার্ড, আমাদের কাঙ্ক্ষিত নাম এবং তারপর একটি সেমিকোলন ব্যবহার করি। কোঁকড়া ধনুর্বন্ধনী বা বৃত্তাকার বন্ধনীর প্রয়োজন নেই! তারপর আমরা subject ভেরিয়েবলে AlwaysEqual-এর একটি ইন্সট্যান্স একইভাবে পেতে পারি: আমরা যে নামটি সংজ্ঞায়িত করেছি সেটি ব্যবহার করে, কোনো কোঁকড়া ধনুর্বন্ধনী বা বৃত্তাকার বন্ধনী ছাড়াই। কল্পনা করুন যে পরবর্তীতে আমরা এই টাইপের জন্য এমন আচরণ প্রয়োগ করব যাতে AlwaysEqual-এর প্রতিটি ইন্সট্যান্স অন্য যেকোনো টাইপের প্রতিটি ইন্সট্যান্সের সমান হয়, সম্ভবত পরীক্ষার উদ্দেশ্যে একটি পরিচিত ফলাফল পাওয়ার জন্য। সেই আচরণ বাস্তবায়ন করার জন্য আমাদের কোনো ডেটার প্রয়োজন হবে না! আপনি চ্যাপ্টার ১০-এ দেখতে পাবেন কীভাবে ট্রেইট সংজ্ঞায়িত করতে হয় এবং সেগুলো ইউনিট-সদৃশ স্ট্রাকট সহ যেকোনো টাইপে প্রয়োগ করতে হয়।

স্ট্রাকট ডেটার ওনারশিপ (Ownership of Struct Data)

Listing 5-1-এর User স্ট্রাকট সংজ্ঞায়, আমরা &str স্ট্রিং স্লাইস টাইপের পরিবর্তে ওনড (owned) String টাইপ ব্যবহার করেছি। এটি একটি ইচ্ছাকৃত পছন্দ কারণ আমরা চাই এই স্ট্রাকটের প্রতিটি ইন্সট্যান্স তার সমস্ত ডেটার মালিক হোক এবং সেই ডেটা যতদিন পর্যন্ত সম্পূর্ণ স্ট্রাকটটি বৈধ ততদিন পর্যন্ত বৈধ থাকুক।

স্ট্রাকটগুলোর জন্য অন্য কিছুর মালিকানাধীন ডেটার রেফারেন্স সংরক্ষণ করাও সম্ভব, কিন্তু সেটি করার জন্য লাইফটাইম (lifetimes) ব্যবহার করতে হবে, একটি Rust ফিচার যা আমরা চ্যাপ্টার ১০-এ আলোচনা করব। লাইফটাইম নিশ্চিত করে যে একটি স্ট্রাকট দ্বারা রেফারেন্স করা ডেটা যতদিন স্ট্রাকটটি বৈধ ততদিন পর্যন্ত বৈধ। ধরুন আপনি লাইফটাইম নির্দিষ্ট না করে একটি স্ট্রাকটে একটি রেফারেন্স সংরক্ষণ করার চেষ্টা করছেন, যেমনটি নিচে দেওয়া হলো; এটি কাজ করবে না:

struct User {
    active: bool,
    username: &str,
    email: &str,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        active: true,
        username: "someusername123",
        email: "someone@example.com",
        sign_in_count: 1,
    };
}

কম্পাইলার অভিযোগ করবে যে এটির লাইফটাইম স্পেসিফায়ার প্রয়োজন:

$ cargo run
   Compiling structs v0.1.0 (file:///projects/structs)
error[E0106]: missing lifetime specifier
 --> src/main.rs:3:15
  |
3 |     username: &str,
  |               ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |     active: bool,
3 ~     username: &'a str,
  |

error[E0106]: missing lifetime specifier
 --> src/main.rs:4:12
  |
4 |     email: &str,
  |            ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |     active: bool,
3 |     username: &str,
4 ~     email: &'a str,
  |

For more information about this error, try `rustc --explain E0106`.
error: could not compile `structs` (bin "structs") due to 2 previous errors

চ্যাপ্টার ১০-এ, আমরা এই এররগুলো কীভাবে ঠিক করতে হয় তা নিয়ে আলোচনা করব যাতে আপনি স্ট্রাকটগুলোতে রেফারেন্স সংরক্ষণ করতে পারেন, কিন্তু আপাতত, আমরা &str-এর মতো রেফারেন্সের পরিবর্তে String-এর মতো ওনড টাইপ ব্যবহার করে এইরকম এররগুলো ঠিক করব।