স্ট্রাক্ট হলো কাস্টম ডেটা টাইপ, যা দিয়ে আপনি একাধিক সম্পর্কিত মানকে একসাথে একটি নাম দেওয়া ও প্যাকেজ করার সুযোগ পান।
একটি স্ট্রাক্ট ডিফাইন করতে struct কিওয়ার্ডটি ব্যবহার করুন, তারপর একটি নাম এবং দ্বিতীয় বন্ধনী, যার ভেতরে ফিল্ডগুলোর নাম ও টাইপ থাকবে:
#[derive(Drop)]
struct User {
active: bool,
username: ByteArray,
email: ByteArray,
sign_in_count: u64,
}
একটি struct-এর ইনস্ট্যান্স তৈরি করতে struct-এর নাম উল্লেখ করুন এবং ফিল্ডগুলোর কী-ভ্যালু জোড়া দিয়ে দ্বিতীয় বন্ধনী ব্যবহার করুন।
let user = User {
active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1
};
একটি মিউটেবল struct ইনস্ট্যান্সের ফিল্ড পড়তে বা বদলাতে ডট নোটেশন ব্যবহার করুন।
let mut user = User {
active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1
};
user.email = "anotheremail@example.com";
Cairo-তে মান ইমিউটেবল, এবং এই বৈশিষ্ট্যের কারণে একই মানে অনেক ভ্যারিয়েবল নির্দেশ করলেও মানটি নিজে স্থির ও অপরিবর্তিত থাকে। একটি ভ্যারিয়েবলের ওনার হলো সেই কোড, যা ওই ভ্যারিয়েবল পড়তে পারে (আর মিউটেবল হলে লিখতেও পারে)।
Cairo-তে ওনারশিপের নিয়মগুলো:
স্কোপ হলো একটি প্রোগ্রামের ভেতরের সেই পরিসর, যেখানে একটি ভ্যারিয়েবল বৈধ থাকে।
fn main() {
// `x` is not valid here, it has not been declared yet
let x: u32 = 3; // `x` is declared, it is valid from this point forward
// do stuff with `x`
} // this scope ended here, `x` goes out of scope and is no longer valid
কোনো ভ্যারিয়েবল স্কোপের বাইরে চলে গেলে সেটি ধ্বংস হয়ে যায়, যা নিশ্চিত করে যে সংশ্লিষ্ট রিসোর্সটি সঠিকভাবে মুক্ত করা হয়।
এটি করা হয় Drop ট্রেইট ইমপ্লিমেন্ট বা ডিরাইভ করে, যা কম্পাইলারকে জানায় যে টাইপটি আর কাজে না লাগলে নিরাপদে ধ্বংস করা যায়।
#[derive(Drop)]
struct A {}
fn main() {
A {}; // this value can be destroyed after going out of scope
}
এই মুহূর্তে Drop ইমপ্লিমেন্টেশন সব টাইপের জন্যই ডিরাইভ করা যায়, শুধু ডিকশনারি (Felt252Dict) এবং ডিকশনারি-সহ টাইপগুলো ছাড়া।
একটি মান মুভ করাও যায়, যা ঘটে যখন একটি মান অন্য একটি ফাংশনে পাঠানো হয়। তখন যে ভ্যারিয়েবলটি ওই মানকে নির্দেশ করছিল সেটি ধ্বংস হয়ে যায় (ফলে ভ্যারিয়েবলটি অকেজো হয়ে পড়ে) এবং একই মান ধরে রাখার জন্য একটি নতুন ভ্যারিয়েবল তৈরি হয়।
fn foo(num: u32) {
// do something with p
}
fn main() {
let num: u32 = 10;
foo(num);
// ownership of `num` was moved into `foo` and is thus no longer valid in this scope
}
মান রিটার্ন করাও সেগুলোকে মুভ করার সমান।
নিচের উদাহরণটি এমন একটি ফাংশন দেখায়, যা কিছু মান রিটার্ন করে:
#[derive(Drop)]
struct A {}
fn main() {
// main now owns a1
let a1 = gives_ownership();
// a2 comes into scope
let a2 = A {};
// a2 is moved into takes_and_gives_back, which also moves its return value into a3
let a3 = takes_and_gives_back(a2);
} // Here, a3 and a1 go out of scope and are dropped. a2 was moved, so nothing happens.
// Moves its return value into the function that calls it
fn gives_ownership() -> A {
let some_a = A {};
some_a
}
// This function takes an instance some_a of A and returns it
fn takes_and_gives_back(some_a: A) -> A {
some_a
}
যেহেতু মুভ হওয়ার পর একটি ভ্যারিয়েবল ধ্বংস হয়ে যায়, তাই কম্পাইলার আমাদেরকে সেটি পরে আবার ব্যবহার করতে দেয় না।
#[derive(Drop)]
struct Point {
x: u128,
y: u128,
}
fn foo(p: Point) {
// do something with p
}
fn main() {
let p = Point { x: 5, y: 10 };
foo(p);
foo(p); // compiler error: Variable was previously moved.
}
এই ক্ষেত্রে আমরা একই মান (অর্থাৎ p ভ্যারিয়েবলের Point স্ট্রাক্ট) দুটি ফাংশন কলে-ই পাঠাতে চেষ্টা করছি।
প্রথমবার foo ফাংশনে ভ্যারিয়েবলটি পাঠানোর পরেই সেটি আর ব্যবহারযোগ্য থাকে না।
এটি সমাধানের একটি উপায় হলো আমাদের স্ট্রাক্টে Copy ট্রেইট ডিরাইভ করা, যা বদলে দেবে একটি Copy ভ্যারিয়েবল ফাংশনে পাঠালে কী হয়: তখন আর মানটি মুভ হবে না, বরং একই মানকে নির্দেশ করা একটি নতুন ভ্যারিয়েবল তৈরি হবে।
এটি একটি ফ্রি অপারেশন, কারণ ভ্যারিয়েবল হলো Cairo-র একটি অ্যাবস্ট্রাকশন আর মান সবসময় ইমিউটেবল।
আপনার টাইপে Copy ট্রেইট ইমপ্লিমেন্ট করতে টাইপের ডেফিনিশনে #[derive(Copy)] অ্যানোটেশনটি যোগ করুন।
#[derive(Copy, Drop)]
struct Point {
x: u128,
y: u128,
}
fn foo(p: Point) {
// do something with p
}
fn main() {
let p = Point { x: 5, y: 10 };
foo(p);
foo(p); // this now works
}
কোনো মান ধ্বংস বা মুভ না করেই সেটি পাঠানোর জন্য Cairo-তে দুটি ফিচার আছে, যাদের বলা হয় references ও snapshots।
স্ন্যাপশট কোনো একটি নির্দিষ্ট সময়ে একটি মানের একটি ইমিউটেবল "ভিউ" দেয়, যা দিয়ে আপনি ভ্যারিয়েবলের স্টেট অ্যাক্সেস করার সময়ও তার ওনারশিপ ধরে রাখতে পারেন। ভ্যারিয়েবলে যেকোনো পরিবর্তন একটি নতুন মেমরি সেল তৈরি করে, ফলে স্ন্যাপশটের ভিউ অপরিবর্তিত থাকে।
একটি স্ন্যাপশট তৈরি করতে শুধু ভ্যারিয়েবল ইনস্ট্যান্সের আগে @ বসান।
// this function takes in a snapshot using `@` with the data-type
fn get_len(arr: @Array<u128>) -> u32 { //
arr.len()
}
fn foo(arr: Array<u128>) {
arr.pop_front();
}
fn main() {
let mut new_arr: Array<u128> = array![1, 2, 3, 4, 5, 6, 7, 8];
let snapshot = @new_arr; // using `@` to create a snapshot of a variable
let len = get_len(snapshot); // pass the snapshot as argument, retaining ownership
foo(new_arr); // `new_arr` is still valid up until here and no further
}
একটি স্ন্যাপশটকে আবার সাধারণ ভ্যারিয়েবলে রূপান্তর করতে আপনি ডিস্ন্যাপ অপারেটর * ব্যবহার করতে পারেন, যা @ অপারেটরের ঠিক বিপরীত।
কেবল যে টাইপগুলো Copy ট্রেইট ইমপ্লিমেন্ট করে, সেগুলোই ডিস্ন্যাপ করা যায়।
fn square(n: @u32) -> u32 {
let n = *n; // we desnap the value
n * n
}
fn main() {
let num: u32 = 10;
let squared = square(@num); // we use `@` to create a snapshot
println!("{} squared = {}", num, squared);
}
রেফারেন্স দিয়ে আপনি ফাংশনে মিউটেবল মান পাঠাতে পারেন এবং সেগুলো স্বয়ংক্রিয়ভাবে ফেরতও পেতে পারেন, আর কলিং কনটেক্সটে ওনারশিপ অটুট থাকে।
এটি করা যায় ref মডিফায়ার দিয়ে, শর্ত হলো ভ্যারিয়েবলটি mut কিওয়ার্ড দিয়ে মিউটেবল হিসেবে ঘোষণা করা থাকতে হবে।
আগের Point উদাহরণটি আমরা Copy ট্রেইটের ওপর নির্ভর না করে বরং মিউটেবল রেফারেন্স ব্যবহার করে লিখতে পারি, আর সেটি তবুও কাজ করবে।
#[derive(Drop)]
struct Point {
x: u128,
y: u128,
}
fn foo(ref p: Point) {
// we can now update the passed values and the changes
// will be visible in the calling context
p.x += 1;
}
fn main() {
let mut p = Point { x: 5, y: 10 };
foo(ref p);
foo(ref p); // this now works even without the Copy trait
println!("p.x = {}", p.x); // p.x = 7
}
আপনার কাছের একটি স্থানীয় স্কুলে খুব সক্রিয় একটি ছাত্র সংসদ আছে। ছাত্র সংসদটি একজন সভাপতির মাধ্যমে পরিচালিত হয় এবং প্রতি ২ বছরে একবার নতুন সভাপতি নির্বাচনের জন্য নির্বাচন অনুষ্ঠিত হয়। এ বছরের নির্বাচনে সিদ্ধান্ত হয়েছিল যে ভোট গণনার জন্য একটি নতুন ডিজিটাল সিস্টেম দরকার।
এই নতুন সিস্টেমটি তৈরি করতে স্কুলের আপনার সহায়তা দরকার।
নতুন ভোটিং সিস্টেমের প্রথম যেসব জিনিস দরকার তার একটি হলো ভোট কাউন্টার। এই কাউন্টারটি দিয়ে কোনো নির্দিষ্ট প্রার্থীর প্রাপ্ত ভোটের হিসাব রাখা যায়।
একটি ফাংশন new_vote_counter তৈরি করুন, যা একজন প্রার্থীর প্রাথমিক ভোটের সংখ্যা গ্রহণ করে এবং প্রদত্ত প্রাথমিক ভোটের সংখ্যা দিয়ে ইনিশিয়ালাইজ করা একটি VoteCounter স্ট্রাক্টের স্ন্যাপশট রিটার্ন করে।
let initial_votes: u32 = 2;
let counter: @VoteCounter = new_vote_counter(initial_votes);
// @VoteCounter {
// value: 2,
// }
এখন আপনার কাছে নতুন কাউন্টার তৈরির একটি উপায় আছে! কিন্তু এখন আপনি বুঝতে পারছেন যে নতুন সিস্টেমের একটি কাউন্টার থেকে ভোটের সংখ্যা বের করারও একটি উপায় দরকার হবে।
একটি ফাংশন vote_count তৈরি করুন, যা একটি কাউন্টার (@VoteCounter) আর্গুমেন্ট হিসেবে নেবে এবং কাউন্টারের ভোটের সংখ্যা রিটার্ন করবে।
let initial_votes: u32 = 3;
let vote_counter: @VoteCounter = new_vote_counter(initial_votes);
let count = vote_count(vote_counter);
// => 3
অবশেষে ভোট গণনার সময় এসেছে! এখন আপনার একটি কাউন্টারের ভোট বাড়ানোর একটি উপায় দরকার।
একটি ফাংশন increment_vote_count তৈরি করুন, যা একটি কাউন্টার (@VoteCounter) এবং একটি ভোটের সংখ্যা আর্গুমেন্ট হিসেবে নেবে, এবং সেই সংখ্যক ভোট দিয়ে কাউন্টারটি বাড়াবে।
let votes: u32 = 3;
let vote_counter: @VoteCounter = new_vote_counter(initial_votes);
let vote_counter: VoteCounter = increment_vote_count(vote_counter, 2);
let count = vote_count(@vote_counter);
// => 5
সব ভোট এখন গণনা হয়ে গেছে, তাই পুরো স্কুলের জন্য ফলাফল ঘোষণার প্রস্তুতি নেওয়ার সময় এসেছে। এজন্য আপনি খেয়াল করলেন যে শুধু ভোটের কাউন্টার থাকলেই যথেষ্ট নয়। ভোটের সংখ্যাকে একজন নির্দিষ্ট প্রার্থীর সাথে সম্পর্কিত করার একটি উপায় থাকা দরকার।
একটি ফাংশন new_election_result তৈরি করুন, যা একজন প্রার্থীর নাম এবং তার ভোটের সংখ্যা গ্রহণ করে এবং একটি নতুন নির্বাচন ফলাফল রিটার্ন করে।
let result: ElectionResult = new_election_result("Peter", 3);
// ElectionResult {
// name: "Peter",
// votes: @VoteCounter {
// value: 3,
// },
// }
নির্বাচন ফলাফলের স্ট্রাক্টটি ইতিমধ্যে আপনার জন্য তৈরি করা হয়েছে এবং এটি এভাবে সংজ্ঞায়িত:
struct ElectionResult {
name: ByteArray,
counter: @VoteCounter,
}
স্কুলকে নতুন সভাপতির কথা জানানোর সময় এসেছে! স্কুলে থাকা ছোট ছোট ডিজিটাল মেসেজ বোর্ডে সভাপতির নাম ঘোষণা করা হবে।
বার্তাটিতে নতুন সভাপতির নাম এবং তার পাওয়া ভোট নিচের ফরম্যাটে দেখাতে হবে: <candidate_name> (<votes>)।
এমন একটি বার্তার উদাহরণ হলো: "Peter (51)"।
একটি ফাংশন display_result তৈরি করুন, যা একটি @ElectionResult আর্গুমেন্ট হিসেবে গ্রহণ করবে এবং দেখানোর জন্য বার্তাসহ একটি ByteArray রিটার্ন করবে।
let result = ElectionResult {
name: "John",
votes: @VoteCounter {
value: 32,
},
};
let msg = display_result(@result);
// => "John (32)"
চূড়ান্ত ফলাফল সঠিক ছিল কিনা নিশ্চিত করার জন্য ভোট আবার গণনা করা হয়েছিল। পুনর্গণনায় দেখা গেল যে কিছু প্রার্থীর ভোটের সংখ্যায় এক ভোটের গরমিল ছিল।
একটি ফাংশন decrement_votes_of_candidate তৈরি করুন, যা চূড়ান্ত ফলাফল এবং এমন একজন প্রার্থীর নাম নেবে যার ভোটের সংখ্যা আপনি কমাবেন।
চূড়ান্ত ফলাফল দেওয়া হয় একটি Felt252Dict<u32> আকারে, যেখানে কী (key) হলো প্রার্থীদের নাম এবং মান হলো তাদের মোট ভোট।
let mut final_results: Felt252Dict<u32> = Default::default();
final_results.insert('Mary', 10);
final_results.insert('John', 51);
decrement_votes_of_candidate(ref final_results, 'Mary');
let decremented_votes = final_results.get('Mary');
// => 9
Exercism-এ সাইন আপ করুন, Cairo ট্র্যাকের 25টি কনসেপ্ট68টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।