Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


x-i18n: generated_at: “2026-03-01T13:44:32Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: 344424e680093f9fd0291e895cd1002c5645a769d3b928de541d78148b93caa7 source_path: ch05-01-defining-structs.md workflow: 16

定义并实例化结构体 (Defining and Instantiating Structs)

Defining and Instantiating Structs

结构体与 “元组类型” 部分讨论的元组类似,因为两者都持有多个相关的值。与元组一样,结构体的各个部分可以是不同的类型。与元组不同的是,在结构体中,你会为每个数据片段命名,以便清楚地了解这些值的含义。添加这些名称意味着结构体比元组更灵活:你不需要依赖数据的顺序来指定或访问实例的值。

Structs are similar to tuples, discussed in “The Tuple Type” 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.

要定义结构体,我们输入关键字 struct 并为整个结构体命名。结构体的名称应该描述被组合在一起的数据片段的意义。然后,在花括号内,我们定义数据片段的名称和类型,我们称之为“字段 (fields)”。例如,示例 5-1 展示了一个存储用户账户信息的结构体。

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.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-01/src/main.rs:here}}
}

要在定义结构体后使用它,我们通过为每个字段指定具体值来创建该结构体的“实例 (instance)”。我们通过声明结构体的名称来创建一个实例,然后添加包含 key: value 对的花括号,其中键是字段的名称,值是我们想要存储在这些字段中的数据。我们不必按照在结构体中声明字段的相同顺序来指定字段。换句话说,结构体定义就像是该类型的通用模板,而实例则用特定数据填充该模板以创建该类型的值。例如,我们可以像示例 5-2 所示那样声明一个特定的用户。

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 a particular user as shown in Listing 5-2.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-02/src/main.rs:here}}
}

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

To get a specific value from a struct, we use dot notation. For example, to access this user’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.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-03/src/main.rs:here}}
}

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

Note that the entire instance must be mutable; Rust doesn’t allow us to mark only certain fields as mutable. As with any expression, we can construct a new instance of the struct as the last expression in the function body to implicitly return that new instance.

示例 5-4 展示了一个 build_user 函数,该函数返回一个具有给定电子邮件和用户名的 User 实例。active 字段获取值 true,而 sign_in_count 获取值 1

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 true, and the sign_in_count gets a value of 1.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-04/src/main.rs:here}}
}

用与结构体字段相同的名称来命名函数参数是有意义的,但必须重复 emailusername 字段名称和变量有点乏味。如果结构体有更多字段,重复每个名称会变得更加烦人。幸运的是,有一个方便的简写!

It makes sense to name the function parameters with the same name as the struct fields, but having to repeat the email and username field names and variables is a bit tedious. If the struct had more fields, repeating each name would get even more annoying. Luckily, there’s a convenient shorthand!

使用字段初始化简写语法 (Using the Field Init Shorthand)

Using the Field Init Shorthand

由于示例 5-4 中的参数名称和结构体字段名称完全相同,我们可以使用“字段初始化简写 (field init shorthand)”语法来重写 build_user,使其行为完全相同,但没有 usernameemail 的重复,如示例 5-5 所示。

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 that it behaves exactly the same but doesn’t have the repetition of username and email, as shown in Listing 5-5.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-05/src/main.rs:here}}
}

在这里,我们正在创建 User 结构体的一个新实例,该结构体有一个名为 email 的字段。我们想要将 email 字段的值设置为 build_user 函数的 email 参数中的值。因为 email 字段和 email 参数具有相同的名称,我们只需要写 email 而不是 email: email

Here, we’re creating a new instance of the User struct, which has a field named email. We want to set the email field’s value to the value in the email parameter of the build_user function. Because the email field and the email parameter have the same name, we only need to write email rather than email: email.

使用结构体更新语法从其他实例创建实例 (Creating Instances with Struct Update Syntax)

Creating Instances with Struct Update Syntax

从同一个类型的另一个实例创建一个包含大部分值但更改其中一些值的新结构体实例通常很有用。你可以使用“结构体更新语法 (struct update syntax)”来做到这一点。

It’s often useful to create a new instance of a struct that includes most of the values from another instance of the same type, but changes some of them. You can do this using struct update syntax.

首先,在示例 5-6 中,我们展示了如何以常规方式(不使用更新语法)在 user2 中创建一个新的 User 实例。我们为 email 设置了一个新值,但在其他方面使用了我们在示例 5-2 中创建的 user1 中的相同值。

First, in Listing 5-6 we show how to create a new User instance in user2 in the regular way, 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.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-06/src/main.rs:here}}
}

使用结构体更新语法,我们可以用更少的代码实现相同的效果,如示例 5-7 所示。语法 .. 指定未显式设置的剩余字段应具有与给定实例中字段相同的值。

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.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-07/src/main.rs:here}}
}

示例 5-7 中的代码也在 user2 中创建了一个实例,该实例的 email 值不同,但 usernameactivesign_in_count 字段的值与 user1 相同。..user1 必须放在最后,以指定任何剩余字段都应从 user1 的相应字段中获取其值,但我们可以选择以任何顺序为任意数量的字段指定值,而不管结构体定义中字段的顺序如何。

The code in Listing 5-7 also creates an instance in user2 that has a different value for email but has the same values for the username, active, and sign_in_count fields from user1. The ..user1 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.

注意,结构体更新语法像赋值一样使用 =;这是因为它会移动数据,正如我们在 “变量与数据交互的方式:移动” 部分看到的那样。在这个例子中,我们在创建 user2 之后不能再使用 user1,因为 user1username 字段中的 String 被移动到了 user2 中。如果我们为 user2emailusername 都提供了新的 String 值,从而仅使用了 user1activesign_in_count 值,那么 user1 在创建 user2 后仍然有效。activesign_in_count 都是实现了 Copy 特征的类型,因此我们在 “只限栈的数据:拷贝” 部分讨论的行为将适用。在这个例子中,我们仍然可以使用 user1.email,因为它的值没有从 user1 中移出。

Note that the struct update syntax uses = like an assignment; this is because it moves the data, just as we saw in the “Variables and Data Interacting with Move” section. In this example, we can no longer use user1 after creating user2 because the String in the username field of user1 was moved into user2. If we had given user2 new String 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 “Stack-Only Data: Copy” section would apply. We can also still use user1.email in this example, because its value was not moved out of user1.

使用没有具名字段的元组结构体创建不同的类型 (Creating Different Types with Tuple Structs)

Creating Different Types with Tuple Structs

Rust 还支持看起来类似于元组的结构体,称为“元组结构体 (tuple structs)”。元组结构体具有结构体名称提供的附加含义,但没有与其字段关联的名称;相反,它们只有字段的类型。当你想要给整个元组起一个名字,并使该元组与其他元组具有不同的类型,且像在常规结构体中那样为每个字段命名会显得冗长或多余时,元组结构体非常有用。

Rust also supports structs that look similar to tuples, called tuple structs. Tuple structs have the added meaning the struct name provides but don’t have names associated with their fields; rather, they just have the types of the fields. Tuple structs are useful when you want to give the whole tuple a name and make the tuple a different type from other tuples, and when naming each field as in a regular struct would be verbose or redundant.

要定义元组结构体,以 struct 关键字和结构体名称开头,后跟元组中的类型。例如,在这里我们定义并使用了两个名为 ColorPoint 的元组结构体:

To define a tuple struct, start with the struct keyword and the struct name followed by the types in the tuple. For example, here we define and use two tuple structs named Color and Point:

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-01-tuple-structs/src/main.rs}}
}

注意,blackorigin 值是不同的类型,因为它们是不同元组结构体的实例。你定义的每个结构体都是它自己的类型,即使结构体内的字段可能具有相同的类型。例如,一个接收 Color 类型参数的函数不能接收 Point 作为参数,即使这两种类型都由三个 i32 值组成。在其他方面,元组结构体实例与元组类似,因为你可以将它们解构为单独的部分,并且可以使用 . 后跟索引来访问单个值。与元组不同,元组结构体要求你在解构它们时命名结构体的类型。例如,我们会写 let Point(x, y, z) = origin; 来将 origin 点中的值解构为名为 xyz 的变量。

Note that the black and origin values are different types because they’re instances of different tuple structs. Each struct you define is its own type, even though the fields within the struct might have the same types. For example, a function that takes a parameter of type Color cannot take a Point as an argument, even though both types are made up of three i32 values. Otherwise, tuple struct instances are similar to tuples in that you can destructure them into their individual pieces, and you can use a . followed by the index to access an individual value. Unlike tuples, tuple structs require you to name the type of the struct when you destructure them. For example, we would write let Point(x, y, z) = origin; to destructure the values in the origin point into variables named x, y, and z.

定义没有任何字段的类单元结构体 (Defining Unit-Like Structs)

Defining Unit-Like Structs

你也可以定义没有任何字段的结构体!这些被称为“类单元结构体 (unit-like structs)”,因为它们的行为类似于 (),即我们在 “元组类型” 部分提到的单元类型。当你需要在某种类型上实现一个特征 (trait) 但没有任何想要存储在类型本身中的数据时,类单元结构体非常有用。我们将在第 10 章讨论特征。这是一个声明并实例化名为 AlwaysEqual 的单元结构体的示例:

You can also define structs that don’t have any fields! These are called unit-like structs because they behave similarly to (), the unit type that we mentioned in “The Tuple Type” section. Unit-like structs can be useful when you need to implement a trait on some type but don’t have any data that you want to store in the type itself. We’ll discuss traits in Chapter 10. Here’s an example of declaring and instantiating a unit struct named AlwaysEqual:

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-04-unit-like-structs/src/main.rs}}
}

要定义 AlwaysEqual,我们使用 struct 关键字、我们想要的名称,然后是一个分号。不需要花括号或圆括号!然后,我们可以以类似的方式在 subject 变量中获取 AlwaysEqual 的实例:使用我们定义的名称,不带任何花括号或圆括号。想象一下,稍后我们将为此类型实现行为,使得 AlwaysEqual 的每个实例始终等于任何其他类型的每个实例,也许是为了获得已知的测试结果。实现该行为不需要任何数据!你将在第 10 章中看到如何定义特征并在任何类型(包括类单元结构体)上实现它们。

To define AlwaysEqual, we use the struct keyword, the name we want, and then a semicolon. No need for curly brackets or parentheses! Then, we can get an instance of AlwaysEqual in the subject variable in a similar way: using the name we defined, without any curly brackets or parentheses. Imagine that later we’ll implement behavior for this type such that every instance of AlwaysEqual is always equal to every instance of any other type, perhaps to have a known result for testing purposes. We wouldn’t need any data to implement that behavior! You’ll see in Chapter 10 how to define traits and implement them on any type, including unit-like structs.

结构体数据的所有权 (Ownership of Struct Data)

Ownership of Struct Data

在示例 5-1 的 User 结构体定义中,我们使用了拥有的 String 类型而不是 &str 字符串切片类型。这是一个深思熟虑的选择,因为我们希望此结构体的每个实例都拥有其所有数据,并希望该数据在整个结构体有效期间保持有效。

In the User struct definition in Listing 5-1, we used the owned String type rather than the &str string slice type. This is a deliberate choice because we want each instance of this struct to own all of its data and for that data to be valid for as long as the entire struct is valid.

结构体也可以存储对归其他东西所有的数据的引用,但这样做需要使用“生命周期 (lifetimes)”,这是我们将在第 10 章讨论的一个 Rust 功能。生命周期确保结构体引用的数据与结构体本身一样有效。假设你尝试在结构体中存储引用而不指定生命周期,如下面 src/main.rs 中的内容;这将无法工作:

It’s also possible for structs to store references to data owned by something else, but to do so requires the use of lifetimes, a Rust feature that we’ll discuss in Chapter 10. Lifetimes ensure that the data referenced by a struct is valid for as long as the struct is. Let’s say you try to store a reference in a struct without specifying lifetimes, like the following in src/main.rs; this won’t work:

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,
    };
}

编译器将抱怨它需要生命周期限定符:

The compiler will complain that it needs lifetime specifiers:

$ 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 章中,我们将讨论如何修复 these 错误,以便你可以在结构体中存储引用,但现在,我们将使用像 String 这样的拥有类型而不是像 &str 这样的引用来修复此类错误。

In Chapter 10, we’ll discuss how to fix these errors so that you can store references in structs, but for now, we’ll fix errors like these using owned types like String instead of references like &str.