Mendefinisikan dan Menginstansiasi Struct

Struct mirip dengan tuple, yang dibahas dalam bagian Tipe Data, karena keduanya menyimpan beberapa nilai terkait. Seperti tuple, bagian-bagian dari sebuah struct dapat memiliki tipe yang berbeda-beda. Berbeda dengan tuple, dalam sebuah struct, Anda akan memberi nama pada setiap bagian data sehingga jelas apa arti dari nilai-nilai tersebut. Menambahkan nama-nama ini berarti bahwa struct lebih fleksibel daripada tuple: Anda tidak perlu bergantung pada urutan data untuk menentukan atau mengakses nilai dari sebuah instance.

Untuk mendefinisikan sebuah struct, kita masukkan kata kunci struct dan memberi nama pada keseluruhan struct. Nama struct sebaiknya menggambarkan signifikansi dari potongan-potongan data yang dikelompokkan bersama. Kemudian, di dalam kurung kurawal, kita mendefinisikan nama dan tipe dari potongan-potongan data tersebut, yang disebut sebagai fields. Sebagai contoh, Listing 5-1 menunjukkan sebuah struct yang menyimpan informasi tentang sebuah akun pengguna.

Filename: src/lib.cairo

#[derive(Drop)]
struct User {
    active: bool,
    username: ByteArray,
    email: ByteArray,
    sign_in_count: u64,
}

Listing 5-1: A User struct definition

Untuk menggunakan sebuah struct setelah kita telah mendefinisikannya, kita membuat sebuah instance dari struct tersebut dengan menentukan nilai konkret untuk setiap fieldnya. Kita membuat sebuah instance dengan menyebutkan nama struct dan kemudian menambahkan kurung kurawal yang berisi pasangan key: value, di mana kunci-kuncinya adalah nama-nama field dan nilai-nilainya adalah data yang ingin kita simpan dalam field-field tersebut. Kita tidak harus menyebutkan field-field tersebut dalam urutan yang sama dengan yang kita deklarasikan dalam struct. Dengan kata lain, definisi struct tersebut seperti template umum untuk tipe data, dan instance mengisi template tersebut dengan data tertentu untuk membuat nilai dari tipe tersebut.

For example, we can declare two particular users as shown in Listing 5-2.

Filename: src/lib.cairo

#[derive(Drop)]
struct User {
    active: bool,
    username: ByteArray,
    email: ByteArray,
    sign_in_count: u64,
}

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

Listing 5-2: Creating two instances of the User struct

To get a specific value from a struct, we use dot notation. For example, to access user1's email address, we use user1.email. If the instance is mutable, we can change a value by using the dot notation and assigning into a particular field. Listing 5-3 shows how to change the value in the email field of a mutable User instance.

Filename: src/lib.cairo

#[derive(Drop)]
struct User {
    active: bool,
    username: ByteArray,
    email: ByteArray,
    sign_in_count: u64,
}
fn main() {
    let mut user1 = User {
        active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1,
    };
    user1.email = "anotheremail@example.com";
}

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

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


Listing 5-3: Changing the value in the email field of a User instance

Perlu diingat bahwa seluruh instance harus mutable; Cairo tidak mengizinkan kita untuk menandai hanya beberapa field tertentu sebagai mutable.

Seperti halnya dengan ekspresi lainnya, kita dapat membuat sebuah instance baru dari struct sebagai ekspresi terakhir dalam tubuh fungsi untuk secara implisit mengembalikan instance baru tersebut.

Listing 5-4 menunjukkan sebuah fungsi build_user yang mengembalikan sebuah instance User dengan email dan username yang diberikan. Field active mendapatkan nilai true, dan field sign_in_count mendapatkan nilai 1.

Filename: src/lib.cairo

#[derive(Drop)]
struct User {
    active: bool,
    username: ByteArray,
    email: ByteArray,
    sign_in_count: u64,
}
fn main() {
    let mut user1 = User {
        active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1,
    };
    user1.email = "anotheremail@example.com";
}

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

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


Listing 5-4: A build_user function that takes an email and username and returns a User instance.

Masuk akal untuk memberi nama parameter fungsi dengan nama yang sama dengan field-field struct, tetapi harus mengulang nama field email dan username serta variabelnya agak menjengkelkan. Jika struct memiliki lebih banyak field, pengulangan setiap nama akan menjadi lebih menjengkelkan. Untungnya, ada sebuah singkatan yang nyaman!

Menggunakan Singkatan Inisialisasi Field

Karena nama-nama parameter dan nama-nama field struct sama persis dalam Listing 5-4, kita dapat menggunakan sintaks singkatan inisialisasi field untuk menulis ulang build_user sehingga berfungsi sama persis tetapi tidak memiliki pengulangan username dan email, seperti yang ditunjukkan dalam Listing 5-5.

Filename: src/lib.cairo

#[derive(Drop)]
struct User {
    active: bool,
    username: ByteArray,
    email: ByteArray,
    sign_in_count: u64,
}
fn main() {
    let mut user1 = User {
        active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1,
    };
    user1.email = "anotheremail@example.com";
}

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

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


Listing 5-5: A build_user function that uses field init shorthand because the username and email parameters have the same name as struct fields.

Di sini, kita sedang membuat sebuah instance baru dari struct User, yang memiliki field bernama email. Kita ingin mengatur nilai field email ke nilai dari parameter email dalam fungsi build_user. Karena field email dan parameter email memiliki nama yang sama, kita hanya perlu menulis email daripada email: email.

Creating Instances from Other Instances with Struct Update Syntax

It’s often useful to create a new instance of a struct that includes most of the values from another instance, but changes some. You can do this using struct update syntax.

First, in Listing 5-6 we show how to create a new User instance in user2 regularly, without the update syntax. We set a new value for email but otherwise use the same values from user1 that we created in Listing 5-2.

Filename: src/lib.cairo

#[derive(Drop)]
struct User {
    active: bool,
    username: ByteArray,
    email: ByteArray,
    sign_in_count: u64,
}

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

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

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


Listing 5-6: Creating a new User instance using all but one of the values from user1

Using struct update syntax, we can achieve the same effect with less code, as shown in Listing 5-7. The syntax .. specifies that the remaining fields not explicitly set should have the same value as the fields in the given instance.

Filename: src/lib.cairo

use core::byte_array;
#[derive(Drop)]
struct User {
    active: bool,
    username: ByteArray,
    email: ByteArray,
    sign_in_count: u64,
}

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

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

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


Listing 5-7: Using struct update syntax to set a new email value for a User instance but to use the rest of the values from user1

The code in Listing 5-7 also creates an instance of user2 that has a different value for email but has the same values for the username, active, and sign_in_count fields as user1. The ..user1 part must come last to specify that any remaining fields should get their values from the corresponding fields in user1, but we can choose to specify values for as many fields as we want in any order, regardless of the order of the fields in the struct’s definition.

Note that the struct update syntax uses = like an assignment; this is because it moves the data, just as we saw in the "Moving Values" section. In this example, we can no longer use user1 as a whole after creating user2 because the ByteArray in the username field of user1 was moved into user2. If we had given user2 new ByteArray values for both email and username, and thus only used the active and sign_in_count values from user1, then user1 would still be valid after creating user2. Both active and sign_in_count are types that implement the Copy trait, so the behavior we discussed in the "Copy Trait" section would apply.