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.