结构体的定义和实例化
结构体和元组类似,他们都包含多个相关的值。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名个部分数据以便能清楚的表明其值的含义。由于有了这些名字,结构体比元组更灵活:不需要以来顺序来指定和访问实例中的值。
定义结构体,需要使用struct
关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义没一部分数据的名字和类型,我们称为字段。例如,以下示例展示了一个存储用户帐号信息的结构体:
struct User{
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的实例。创建一个实例需要以结构体的名字开头,接着在大括号中使用key: value
键值对的形式提供字段,其中key是字段的名字,value是需要存储在字段中的数据值。实例中字段的顺序不需要和他们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。以下例子声明了一个特定的用户:
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
}
为了从结构体中获取某个特定的值,可以使用点号。举个例子,想要用户的邮箱地址,可以使用user1.email
。如果结构体的实例是可变的,我们可以使用点号并为对应的字段赋值。以下实例展示了如何改变一个可变的User
实例中email
字段的值:
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
}
注意整个实例必须是可变的;Rust并不允许只将某个字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式的返回这个实例。
以下例子定义了一个build_user
函数,它返回了一个带有给定的email和用户名的User
结构体实例。active
字段的值为true
,并且sign_in_count
的值为1.
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: username,
email: email,
sign_in_count: 1
}
}
为函数参数起与结构体字段相同的名字是可以理解的,但是不得不重复email
和username
字段名称与变量有些罗嗦。如果结构体有更多字段,重复每个名称就更加烦人了。可以使用方便的简写语法。
使用字段初始化简写语法
以上示例中的参数名和字段名都完全相同,我们可以使用字段初始化简写语法来重写build_user
,这样其行为与之前完全相同,不过无需重复username
和email
了,如下例所示。
fn build_user_new(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1
}
}
这里我们创建了一个新的User
结构化实例,它有一个叫做email
的字段。我们想要将email
字段的值设置为build_user
函数email
的参数的值。因为email
字段与email
参数有着相同的名称,则只需要编写email
而不是email: email
;
使用结构体更新语法从其他实例创建实例
使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例通常是很有用的。这可以通过结构体更新语法实现。
以下例子展示不使用更新语法时,如何在user2
中创建一个新的User
实例。我们为email
设置了新的值,其他值则使用了上述例子中创建的user1
中的同名值:
fn main() {
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.conm"),
sign_in_count: user1.sign_in_count
};
}
使用结构体更新语法,我们可以通过更少的代码来达到相同的效果,如下例所示..
语法制定了剩余未显式设置值的字段应有与给定实例对应字段相同的值。
fn main() {
let user3 = User {
email: String::from("another@example.com"),
..user2
};
}
上述例子中的代码也在user2
中创建了一个新实例,但该实例中email
字段的值和user2
不同,而其余字段的值与user2
相同。user2
必须放在最后,以制定其余的字段应从user2
的相应字段中获取其值,但我们可以选择以任何顺序为任意字段指定值,而不用考虑结构体定义中字段的顺序。
使用没有命名字段的元组结构体来创建不同的类型
也可以定义与元组类似的结构体,称为元组结构体。元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。当你想给整个元组取一个名字,并使元组成为与其他元组不同的类型时,元组结构体是很有用的,这时像常规结构体那样为每个字段命名就显得多余和形式化了。
要定义元组结构体,以struct
关键字和结构体命名开头并后跟元组中的类型,例如,下面是两个分别叫做Point
和Color
结构体的定义和用法:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
注意black
和origin
值的类型不同,因为它们是不同的元组结构体的实例。定义的每一个结构体都有其自己的类型,即使结构体中的字段可能有着相同的类型。例如,一个获取Color
类型参数的函数不能接受Point
作为参数,即便这两个类型都由三个i32
值组成。在其他方面,元组结构体类似于元组,可以将它们结构为单独的部分,也可以使用.
后跟索引来访问单独的值。
没有任何字段的类单元结构体
我们也可以定义一个没有任何字段的结构体,他们被成为类单元结构体因为他们类似于()
,即“元组类型“一节中提到的uint类型。类单元结构体常常在你想要在某个类型上实现trait但不需要在类型中存储数据的时候发挥作用。下面是一个生命和实例化一个名为AlwaysEqual
的uint结构的例子。
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
要定义AlwaysEqual
,我们使用struct
关键字,我们想要的名称,然后是一个分号。不需要元括号或花括号。
结构体数据所有权
在User结构体的定义中,我们使用了自身拥有所有权的String
类型而不是&str
字符串slice类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。
可以使结构体存储被其他对象拥有的数据的引用,不过那么做的话需要用上生命周期。