开发环境
- Windows 10
- Rust 1.56.1
- VS Code 1.62.3
项目工程
这里继续沿用上次工程rust-demo
结构体
结构体类似于元组。与元组一样,结构的碎片可以是不同的类型。与元组不同的是,我们将命名每个数据段,这样就可以清楚地了解值的含义。由于这些名称,结构体比元组更灵活:我们不必依赖数据的顺序来指定或访问实例的值。
要定义结构,我们输入关键字struct并命名整个结构体。结构体的名称应该描述将数据块组合在一起的重要性。然后,在花括号中定义数据片段的名称和类型,我们称之为字段。示例如下:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
为了在定义结构体之后使用它,我们通过为每个字段指定具体的值来创建该结构的一个实例。我们通过声明结构的名称来创建一个实例,然后添加包含key:value对的花括号,其中key是字段的名称,value是我们想要存储在这些字段中的数据。我们不必按照在结构中声明字段的顺序指定字段。换句话说,结构体定义类似于类型的通用模板,实例使用特定的数据填充该模板,以创建类型的值。示例如下:
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
要从结构体中得到一个特定的值,我们可以使用点表示法。如果我们只想要这个用户的电子邮件地址,我们可以使用user1.email,只要我们想使用这个值。如果实例是可变的,我们可以通过使用点表示法并将其赋值到特定字段来更改值。如下所示:
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// 使用结构体User中的email成员
user1.email = String::from("anotheremail@example.com");
请注意,整个实例必须是可变的;Rust不允许我们只将某些字段标记为可变的。与任何表达式一样,我们可以构造一个结构体的新实例,作为函数体中的最后一个表达式,以隐式返回该新实例。如下所示:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
// 构建结构体User
fn build_user(email: String, username: String) -> User {
User {
email: email,
username: username,
active: true,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("someone@example.com"),
String::from("someusername123"),
);
}
将函数参数命名为结构体字段相同的名称是有意义的,但是必须重复email和username字段名和变量有点繁琐。如果结构体有更多的字段,重复每个名称将变得更加烦人。幸运的是,有一个方便的速记!
使用字段Init
由于参数名称和结构体字段名和上述示例完全相同,我们可以使用字段init速记语法重写build_user,使其行为完全相同,但不会重复email和username。如下所示:
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
在这里,我们正在创建一个新的用户结构体实例,其中有一个名为email的字段。我们希望将email字段的值设置为build_user函数的email参数中的值。因为“email”字段和“email”参数具有相同的名称,所以我们只需要编写email,而不是email:email。
从其他实例创建实例
创建结构体的新实例通常是有用的,它使用了大多数旧实例的值,但更改了一些值。可以使用结构体更新语法来完成此操作。如下所示:
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
使用结构体更新语法,我们可以使用更少的代码实现同样的效果。语法..。指定未显式设置的其余字段应具有与给定实例中的字段相同的值。如下所示:
// 使用更新语法..构建结构体实例
let user2 = User {
email: String::from("another@example.com"),
..user1
};
上述代码中还在user2中创建了一个实例,该实例具有不同的email,但与user 1中的username、ative和signin_count字段的值相同。..user1必须最后指定任何剩余字段应该从user1中的相应字段中获取值,但是我们可以选择以任意顺序为任意多个字段指定值,而不管结构定义中字段的顺序如何。
使用元组结构
还可以定义看起来类似于元组的结构体,称为元组结构体。元组结构体具有添加的意思,即结构体名称提供,但不具有与其字段相关联的名称;相反,它们只具有字段的类型。当希望为整个元组命名并使元组成为与其他元组不同的类型时,元组结构体非常有用,并且将每个字段命名为常规结构将是冗长或多余的。
要定义元组结构体,请从struct关键字和结构体名称开始,后面跟着元组中的类型。如下所示:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
注意,black和origin是不同的类型,因为它们是不同元组结构的实例。我们定义的每个结构都是自己的类型,即使结构体中的字段具有相同的类型。例如,接受Color类型参数的函数不能将Point作为参数,即使这两种类型都由三个i32值组成。否则,元组结构实例的行为就像元组:我们可以将它们分解为它们的各个部分,我们可以使用一个.后面跟着访问单个值的索引,依此类推。
无字段的单元式结构体
还可以定义没有任何字段的结构体,这些被称为unit-like结构体,因为它们的行为类似于()。unit-like结构体在需要在某种类型上实现一个特性但不想存储在类型本身中的任何数据的情况下是有用的。如下所示:
fn main() {
struct AlwaysEqual;
let subject = AlwaysEqual;
}
要定义AlwaysEquate,我们使用struct关键字,接着结构体的名称,然后是分号。不需要花括号或括号!然后,我们可以以类似的方式用变量subject获得AlwaysEquate的一个实例:使用我们定义的名称,没有任何花括号或括号。想象一下,我们将为这种类型实现行为,即每个实例总是与每一个其他类型的每个实例相等,为了测试目的,可能是为了获得一个已知的结果。我们不需要任何数据来实现这种行为!
结构体数据的所有权
在前面的User结构体定义中,我们使用了自己的字符串类型,而不是&str String字符串切片类型。这是一个深思熟虑的选择,因为我们希望该结构的实例拥有它的所有数据,并且只要整个结构有效,该数据就会有效。
结构体可以存储对其他东西拥有的数据的引用,但是这样做需要使用lifetimes,lifetime确保结构体所引用的数据在结构体的有效期内有效。假设要尝试将引用存储在一个结构体中,而不指定生命周期,就像如下示例,是行不通的。
struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}
运行上述代码
cargo run
本章重点
- 结构体概念
- 定义结构体
- 实例化结构体