Definición e instanciación de estructuras
Structs are similar to tuples, discussed in the Data Types section, in that both hold multiple related values. Like tuples, the pieces of a struct can be different types. Unlike with tuples, in a struct you’ll name each piece of data so it’s clear what the values mean. Adding these names means that structs are more flexible than tuples: you don’t have to rely on the order of the data to specify or access the values of an instance.
To define a struct, we enter the keyword struct
and name the entire struct. A struct’s name should describe the significance of the pieces of data being grouped together. Then, inside curly brackets, we define the names and types of the pieces of data, which we call fields. For example, Listing 5-1 shows a struct that stores information about a user account.
Filename: src/lib.cairo
#[derive(Drop)]
struct User {
active: bool,
username: ByteArray,
email: ByteArray,
sign_in_count: u64,
}
To use a struct after we’ve defined it, we create an instance of that struct by specifying concrete values for each of the fields. We create an instance by stating the name of the struct and then add curly brackets containing key: value pairs, where the keys are the names of the fields and the values are the data we want to store in those fields. We don’t have to specify the fields in the same order in which we declared them in the struct. In other words, the struct definition is like a general template for the type, and instances fill in that template with particular data to create values of the type.
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",
};
}
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 }
}
Tenga en cuenta que toda la instancia debe ser mutable; Cairo no nos permite marcar solo ciertos campos como mutables.
Como con cualquier expresión, podemos construir una nueva instancia de la estructura como la última expresión en el cuerpo de la función para devolver implícitamente esa nueva instancia.
Listing 5-4 shows a build_user
function that returns a User
instance with the given email and username. The active
field gets the value of true
, and the sign_in_count
gets a value of 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 }
}
Tiene sentido nombrar los parámetros de la función con el mismo nombre que los campos de la estructura, porque tener que repetir los nombres y variables de los campos email
y username
es un poco tedioso. Si la estructura tuviera más campos, repetir cada nombre sería aún más molesto. ¡Afortunadamente, hay una forma abreviada!
Using the Field Init Shorthand
Because the parameter names and the struct field names are exactly the same in Listing 5-4, we can use the field init shorthand syntax to rewrite build_user
so it behaves exactly the same but doesn’t have the repetition of username
and email
, as shown in 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 }
}
Aquí, estamos creando una nueva instancia de la estructura User
, que tiene un campo llamado email
. Queremos establecer el valor del campo email
con el valor del parámetro email
de la función build_user
. Debido a que el campo email
y el parámetro email
tienen el mismo nombre, solo necesitamos escribir email
en lugar de 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,
};
}
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 };
}
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.