定义和实例化结构

结构体类似于 Tuples ,在 “Tuples Type” 一节中讨论,因为两者都包含多个相关值。与元组一样, 结构体的片段可以是不同的类型。与元组不同,在 struct 您将为每条数据命名,以便清楚地了解值的含义。添加这些 names 意味着结构体比 Tuples 更灵活:您不必依赖 按数据的顺序指定或访问实例的值。

要定义结构体,我们输入关键字并命名整个结构体。一个 struct 的名称应该描述数据片段的重要性 组合在一起。然后,在大括号内,我们定义 数据片段,我们称之为 fields。例如,示例 5-1 显示了一个 结构体,用于存储有关用户帐户的信息。struct

文件名: src/main.rs
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {}
示例 5-1:结构体定义User

要在定义结构后使用它,我们创建该结构的实例 通过为每个字段指定具体值。我们通过以下方式创建实例 说明结构的名称,然后添加包含 key 的大括号: value 对,其中 keys 是字段的名称,values 是 数据。我们不必在 我们在 struct 中声明它们的顺序相同。换句话说, struct definition 就像该类型的通用模板,实例填充 在该模板中,创建该类型的值。为 例如,我们可以声明一个特定的用户,如图 5-2 所示。

文件名: src/main.rs
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };
}
示例 5-2:创建结构体的实例User

要从结构体中获取特定值,我们使用点表示法。例如,到 访问此用户的电子邮件地址时,我们使用 。如果实例为 可变的,我们可以通过使用点表示法并赋值为 特定字段。示例 5-3 展示了如何更改可变实例 field 中的值。user1.emailemailUser

文件名: src/main.rs
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

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");
}
示例 5-3:更改实例字段中的值emailUser

请注意,整个实例必须是可变的;Rust 不允许我们标记 只有某些字段是可变的。与任何表达式一样,我们可以构造一个新的 实例作为函数体中的最后一个表达式,以 隐式返回该新实例。

示例 5-4 显示了一个函数,该函数返回一个实例 给定的电子邮件和用户名。该字段获取 的值 ,并且 的值为 。build_userUseractivetruesign_in_count1

文件名: src/main.rs
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username: username,
        email: email,
        sign_in_count: 1,
    }
}

fn main() {
    let user1 = build_user(
        String::from("someone@example.com"),
        String::from("someusername123"),
    );
}
示例 5-4:一个接受 email 和 username 并返回实例的函数build_userUser

使用与 struct 相同的名称命名函数参数是有意义的 字段,但必须重复 and 字段名称 和 variables 有点乏味。如果结构体具有更多字段,则重复每个名称 会变得更烦人。幸运的是,有一个方便的速记!emailusername

使用 Field init 简写

因为 parameter name 和 struct field name 在 示例 5-4,我们可以使用 field init 速记语法来重写,这样它的行为就完全一样了,但没有 and 的重复,如示例 5-5 所示。build_userusernameemail

文件名: src/main.rs
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username,
        email,
        sign_in_count: 1,
    }
}

fn main() {
    let user1 = build_user(
        String::from("someone@example.com"),
        String::from("someusername123"),
    );
}
示例 5-5:一个使用 field init 简写的函数,因为 and 参数与 struct 字段同名build_userusernameemail

在这里,我们将创建结构的新实例,该实例具有 field 叫。我们希望将字段的值设置为函数参数中的值。因为 field 和 参数具有相同的名称,我们只需要写 instead 比。Useremailemailemailbuild_useremailemailemailemail: email

使用 Struct Update 语法从其他实例创建实例

创建包含大部分 的值,但会更改一些值。您可以使用 struct update 语法来执行此作。

首先,在示例 5-6 中,我们展示了如何定期创建新实例,而无需 update 语法。我们为 but 设置了一个新值 否则使用我们在示例 5-2 中创建的相同值。Useruser2emailuser1

文件名: src/main.rs
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    // --snip--

    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };
}
示例 5-6:使用 除 1 个值之外的所有值创建新实例Useruser1

使用 struct update 语法,我们可以用更少的代码实现相同的效果,因为 如示例 5-7 所示。语法指定其余字段不 explicitly set 的值应与给定实例中的字段相同。..

文件名: src/main.rs
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    // --snip--

    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}
示例 5-7:使用 struct update 语法为实例设置新值,但使用emailUseruser1

示例 5-7 中的代码还创建了一个实例,该实例具有 的值不同,但 、 和 字段的值相同。必须到最后 要指定任何剩余字段都应从 中的相应字段,但我们可以选择为 AS 指定值 许多字段,无论 结构的定义。user2emailusernameactivesign_in_countuser1..user1user1

请注意,struct update 语法的使用类似于赋值;这是因为 它移动数据,就像我们在“变量和数据交互 移动“部分。在此示例中,创建后不能再作为一个整体使用,因为字段中的 已移动到 中。如果我们为 和 都给出了新值,因此只使用了 中的 和 值,那么 仍然是 创建 后有效。Both 和 都是 实现 trait,因此我们在“Stack-Only Data: Copy“部分将适用。在此示例中,我们仍然可以使用,因为它的值没有移出。=user1user2Stringusernameuser1user2user2Stringemailusernameactivesign_in_countuser1user1user2activesign_in_countCopyuser1.email

使用不带命名字段的 Tuple 结构创建不同的类型

Rust 还支持看起来类似于元组的结构,称为元组结构。 元组结构具有结构名称提供的附加含义,但没有 与其字段关联的名称;相反,它们只有 领域。当你想给整个元组起个名字时,元组结构很有用 并使元组成为与其他元组不同的类型,并在将每个 field 将是 verbose 或 redundant。

要定义元组结构,请从关键字和结构名称开始 后跟元组中的类型。例如,这里我们定义并使用两个 名为 和 的元组结构 :structColorPoint

文件名: src/main.rs
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

请注意,和 值是不同的类型,因为它们是 不同元组结构的实例。您定义的每个结构体都是其自己的类型 即使 struct 中的字段可能具有相同的类型。为 例如,采用 type 为 a 的参数的函数不能将 a 作为参数,即使这两种类型都由三个值组成。否则,元组结构实例类似于元组,因为你可以 将它们分解为单独的部分,你可以使用 Followed 通过索引访问单个值。blackoriginColorPointi32.

没有任何字段的类似 Unit 的结构体

您还可以定义没有任何字段的结构体!这些被称为单元状结构,因为它们的行为类似于 ,即 我们在 “元组类型” 一节中提到过。类单元 当你需要在某种类型上实现 trait 但不需要时,结构体可能很有用 具有要存储在类型本身中的任何数据。我们将讨论特征 在第 10 章中。下面是一个声明和实例化 unit 结构体的示例 叫:()AlwaysEqual

文件名: src/main.rs
struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

要定义 ,我们使用关键字、我们想要的名称和 然后是分号。无需大括号或圆括号!然后我们可以得到一个 实例:使用 name 定义的,没有任何大括号或圆括号。想象一下以后 我们将为这种类型实现行为,使得 的每个实例 of 始终等于任何其他类型的每个实例,也许等于 具有用于测试目的的已知结果。我们不需要任何数据来 实现该行为!您将在第 10 章中看到如何定义 trait 和 在任何类型的结构体上实现它们,包括类似单元的结构体。AlwaysEqualstructAlwaysEqualsubjectAlwaysEqual

结构体数据的所有权

在示例 5-1 的结构体定义中,我们使用了 owned 类型而不是字符串 slice 类型。这是一个经过深思熟虑的选择 因为我们希望这个结构体的每个实例都拥有它的所有数据,并且对于 只要整个结构有效,该数据就有效。UserString&str

结构也可以存储对某物所拥有的数据的引用 else,但要做到这一点,需要使用 lifetimes,我们将 在第 10 章中讨论。生命周期确保结构体引用的数据 只要结构有效,就有效。假设你尝试存储一个引用 在未指定生命周期的结构体中,如下所示;这不起作用:

文件名: src/main.rs
struct User {
    active: bool,
    username: &str,
    email: &str,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        active: true,
        username: "someusername123",
        email: "someone@example.com",
        sign_in_count: 1,
    };
}

编译器会抱怨它需要生命周期说明符:

$ cargo run
   Compiling structs v0.1.0 (file:///projects/structs)
error[E0106]: missing lifetime specifier
 --> src/main.rs:3:15
  |
3 |     username: &str,
  |               ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |     active: bool,
3 ~     username: &'a str,
  |

error[E0106]: missing lifetime specifier
 --> src/main.rs:4:12
  |
4 |     email: &str,
  |            ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |     active: bool,
3 |     username: &str,
4 ~     email: &'a str,
  |

For more information about this error, try `rustc --explain E0106`.
error: could not compile `structs` (bin "structs") due to 2 previous errors

在第 10 章中,我们将讨论如何修复这些错误,以便您可以存储 引用,但现在,我们将使用 owned 修复此类错误 类型,而不是引用 .String&str

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准