ساختار نوع دادهای سفارشی است که به شما امکان میدهد چندین مقدار مرتبط را نامگذاری کنید و آنها را در کنار هم بستهبندی کنید.
برای تعریف یک ساختار، از کلیدواژهی 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
وقتی متغیری از محدوده خارج میشود، از بین میرود؛ این کار تضمین میکند که منبع مرتبط با آن بهدرستی آزاد شود.
این کار با پیادهسازی یا مشتق کردن trait 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.
}
در این حالت، میخواهیم یک مقدار یکسان (ساختار Point در متغیر p) را به هر دو فراخوانی تابع پاس بدهیم.
به محض اینکه بار اول متغیر را به تابع foo پاس بدهیم، دیگر نمیتوان از آن متغیر استفاده کرد.
یکی از راههای حل این مسئله، مشتق کردن trait Copy روی ساختارمان است؛ این کار رفتار پاس دادن یک متغیر Copy به تابع را تغییر میدهد: دیگر مقدار منتقل نمیشود، بلکه متغیر تازهای ساخته میشود که به همان مقدار اشاره میکند.
این کار عملیاتی رایگان است، چون متغیرها یک انتزاع در Cairo هستند و مقدارها همیشه تغییرناپذیرند.
برای پیادهسازی trait 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
}
برای تبدیل یک اسنپشات به یک متغیر معمولی، میتوانید از عملگر desnap یعنی * استفاده کنید که نقطهی مقابل عملگر @ است.
فقط نوعهایی که trait Copy را پیادهسازی کردهاند میتوانند desnap شوند.
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 را طوری تغییر دهیم که بهجای تکیه بر trait 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> داده میشوند، که کلیدهای آن اسم نامزدها و مقادیر آن مجموع آرای هر نامزد است.
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