结构体的定义和实例化
结构体与数据类型一节中讨论的元组类似,它们都包含多个相关的值。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些命名,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
定义结构体,需要使用 struct
关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字和类型,我们称为 字段(field)。例如,示例 5-1 展示了一个存储用户账号信息的结构体。
文件名: 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.
文件名: 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.
文件名: 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, }
}
注意,整个实例必须是可变的;Cairo不允许我们只把某些字段标记为可变的。
与任何表达式一样,我们可以在函数主体的最后一个表达式中构造一个新的结构体实例,以隐式返回该新实例。
示例5-4显示了一个build_user
函数,该函数返回一个User
实例,并给出了电子邮件和用户名。active
字段的值为true
,sign_in_count
的值为1
。
文件名: 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, }
}
为函数参数起与结构体字段相同的名字是可以理解的,但必须重复email
和username
字段的名称和变量就有点乏味了。如果结构体有更多字段,重复每个名称就更加烦人了。幸运的是,有一个方便的简写语法!
Using the Field Init Shorthand
因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用字段初始化简写语法(field init shorthand)来重写 build_user
。如示例 5-5 所示,重写后其行为与之前完全相同,不过无需重复 username
和 email
了。
文件名: 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, }
}
这里,我们正在创建一个新的 User
结构体实例,它有一个名为 email
的字段。我们希望将email
字段的值设置为build_user
函数的email
参数中的值。因为email
字段和email
参数有相同的名字,我们只需要写email
而不是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.
文件名: 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.
文件名: 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.