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

struct ডিফাইন এবং ইনস্ট্যানশিয়েট করা (Defining and Instantiating Structs)

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

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

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

fn main() {}

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

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,
    };
}

একটি struct থেকে একটি নির্দিষ্ট মান পেতে, আমরা ডট নোটেশন (dot notation) ব্যবহার করি। উদাহরণস্বরূপ, এই ব্যবহারকারীর ইমেল ঠিকানা অ্যাক্সেস করতে, আমরা user1.email ব্যবহার করি। যদি ইনস্ট্যান্সটি পরিবর্তনযোগ্য (mutable) হয়, আমরা ডট নোটেশন ব্যবহার করে এবং একটি নির্দিষ্ট ফিল্ডে মান অ্যাসাইন করে একটি মান পরিবর্তন করতে পারি। তালিকা ৫-৩ দেখাচ্ছে কীভাবে একটি পরিবর্তনযোগ্য 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");
}

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

তালিকা ৫-৪ একটি 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"),
    );
}```

</Listing>

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

<!-- Old heading. Do not remove or links may break. -->

<a id="using-the-field-init-shorthand-when-variables-and-fields-have-the-same-name"></a>

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

যেহেতু তালিকা ৫-৪-এ প্যারামিটারের নাম এবং `struct` ফিল্ডের নাম ঠিক একই, তাই আমরা `build_user` ফাংশনটিকে পুনরায় লেখার জন্য _ফিল্ড ইনিশিয়ালাইজেশন শর্টহ্যান্ড_ (field init shorthand) সিনট্যাক্স ব্যবহার করতে পারি যাতে এটি ঠিক একইভাবে আচরণ করে কিন্তু `username` এবং `email`-এর পুনরাবৃত্তি না থাকে, যেমনটি তালিকা ৫-৫-এ দেখানো হয়েছে।

<Listing number="5-5" file-name="src/main.rs" caption="একটি `build_user` ফাংশন যা ফিল্ড ইনিশিয়ালাইজেশন শর্টহ্যান্ড ব্যবহার করে কারণ `username` এবং `email` প্যারামিটারগুলোর নাম `struct` ফিল্ডগুলোর নামের সমান">

```rust
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 struct-এর একটি নতুন ইনস্ট্যান্স তৈরি করছি, যার email নামে একটি ফিল্ড রয়েছে। আমরা email ফিল্ডের মান build_user ফাংশনের email প্যারামিটারের মানে সেট করতে চাই। যেহেতু email ফিল্ড এবং email প্যারামিটারের নাম একই, আমাদের কেবল email লিখতে হবে, email: email লেখার পরিবর্তে।

স্ট্রাকট আপডেট সিনট্যাক্স দিয়ে অন্য ইনস্ট্যান্স থেকে ইনস্ট্যান্স তৈরি

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

প্রথমে, তালিকা ৫-৬-এ আমরা আপডেট সিনট্যাক্স ছাড়া নিয়মিতভাবে user2-তে একটি নতুন User ইনস্ট্যান্স কীভাবে তৈরি করতে হয় তা দেখাই। আমরা email-এর জন্য একটি নতুন মান সেট করি কিন্তু অন্যথায় তালিকা ৫-২-এ তৈরি করা 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,
    };
}

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

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

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

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

বিভিন্ন টাইপ তৈরি করতে নামবিহীন ফিল্ড সহ টাপল স্ট্রাকট ব্যবহার

রাস্ট টাপলের মতো দেখতে struct সমর্থন করে, যাকে টাপল স্ট্রাকট (tuple structs) বলা হয়। টাপল স্ট্রাকটগুলোতে struct নামের অতিরিক্ত অর্থ থাকে তবে তাদের ফিল্ডগুলোর সাথে কোনো নাম যুক্ত থাকে না; বরং, তাদের কেবল ফিল্ডগুলোর টাইপ থাকে। টাপল স্ট্রাকটগুলো দরকারী যখন আপনি পুরো টাপলটিকে একটি নাম দিতে চান এবং টাপলটিকে অন্য টাপল থেকে একটি ভিন্ন টাইপ করতে চান, এবং যখন একটি নিয়মিত struct-এর মতো প্রতিটি ফিল্ডের নামকরণ করা ভার্বোস (verbose) বা অপ্রয়োজনীয় (redundant) হবে।

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

কোনো ফিল্ড ছাড়া ইউনিট-লাইক স্ট্রাকট

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

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

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

স্ট্রাকট ডেটার মালিকানা (Ownership of Struct Data)

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

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

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

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