trpl-zh-cn/src/ch05-01-defining-structs.md
2017-12-05 17:31:45 +08:00

9.7 KiB
Raw Blame History

定义并实例化结构体

ch05-01-defining-structs.md
commit 56352c28cf3fe0402fa5a7cba73890e314d720eb

我们在第三章讨论过,结构体与元组类似。就像元组,结构体的每一部分可以是不同类型。不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。

定义结构体,需要使用 struct 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,它们被称作 字段field),并定义字段类型。例如,示例 5-1 展示了一个储存用户账号信息的结构体:

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

示例 5-1User 结构体定义

一旦定义了结构体后为了使用它,通过为每个字段指定具体值来创建这个结构体的 实例。创建一个实例需要以结构体的名字开头,接着在大括号中使用 key: value 对的形式提供字段,其中 key 是字段的名字value 是需要储存在字段中的数据值。实例中具体说明字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:

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

示例 5-2创建 User 结构体的实例

为了从结构体中获取某个值,可以使用点号。如果我们只想要用户的邮箱地址,可以用 user1.email。要更改结构体中的值,如果结构体的实例是可变的,我们可以使用点号并对对应的字段赋值。示例 5-3 展示了如何改变一个可变的 User 实例 email 字段的值:

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

user1.email = String::from("anotheremail@example.com");

示例 5-3改变 User 结构体 email 字段的值

与其他任何表达式一样,我们可以在函数体的最后一个表达式构造一个结构体,从函数隐式的返回一个结构体的新实例。表 5-4 显示了一个返回带有给定的 emailusernameUser 结构体的实例的 build_user 函数。active 字段的值为 true,并且 sign_in_count 的值为 1

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

示例 5-4build_user 函数获取 email 和用户名并返回 User 实例

不过,重复 email 字段与 email 变量的名字,同样的对于username,感觉有一点无趣。将函数参数起与结构体字段相同的名字是可以理解的,但是如果结构体有更多字段,重复它们是十分烦人的。幸运的是,这里有一个方便的语法!

变量与字段同名时的字段初始化语法

如果有变量与字段同名的话,你可以使用 字段初始化语法field init shorthand)。这可以让创建新的结构体实例的函数更为简练。

在示例 5-4 中,名为 emailusername 的参数与结构体 User 的字段 emailusername 同名。因为名字相同,我们可以写出不重复 emailusernamebuild_user 函数,如示例 5-5 所示。 这个版本的函数与示例 5-4 中代码的行为完全相同。这个字段初始化语法可以让这类代码更简洁,特别是当结构体有很多字段的时候。

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

示例 5-5build_user 函数使用了字段初始化语法,因为 emailusername 参数与结构体字段同名

使用结构体更新语法从其他对象创建对象

可以从老的对象创建新的对象常常是很有帮助的,即复用大部分老对象的值并只改变一部分。示例 5-6 展示了一个设置 emailusername 的值但其余字段使用与示例 5-2 中 user1 实例相同的值以创建新的 User 实例 user2 的例子:

# struct User {
#     username: String,
#     email: String,
#     sign_in_count: u64,
#     active: bool,
# }
#
# 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"),
    username: String::from("anotherusername567"),
    active: user1.active,
    sign_in_count: user1.sign_in_count,
};

示例 5-6创建 User 新实例,user2,并将一些字段的值设置为 user1 同名字段的值

结构体更新语法struct update syntax)可以利用更少的代码获得与示例 5-6 相同的效果。结构体更新语法利用 .. 以指定未显式设置的字段应有与给定实例对应字段相同的值。示例 5-7 中的代码同样地创建了有着不同的 emailusername 值但 activesign_in_count 字段与 user1 相同的实例 user2

# struct User {
#     username: String,
#     email: String,
#     sign_in_count: u64,
#     active: bool,
# }
#
# 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"),
    username: String::from("anotherusername567"),
    ..user1
};

示例 5-7使用结构体更新语法为 User 实例设置新的 emailusername 值,但使用 user1 变量中剩下字段的值

使用没有命名字段的元组结构体创建不同的类型

也可以定义与元组相像的结构体,称为 元组结构体tuple structs),有着结构体名称提供的含义,但没有具体的字段名只有字段的类型。元组结构体的定义仍然以struct 关键字与结构体名称,接下来是元组的类型。如以下是命名为 ColorPoint 的元组结构体的定义与使用:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

注意 blackorigin 变量是不同的类型,因为它们是不同的元组结构体的实例。我们定义的每一个结构体有着自己的类型,即使结构体中的字段有相同的类型。在其他方面,元组结构体类似我们在第三章提到的元组。

没有任何字段的类单元结构体

我们也可以定义一个没有任何字段的结构体!它们被称为 类单元结构体unit-like structs)因为它们类似于 (),即 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型内存储数据的时候发挥作用。我们将在第十章介绍 trait。

结构体数据的所有权

在示例 5-1 中的 User 结构体的定义中,我们使用了自身拥有所有权的 String 类型而不是 &str 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也应该是有效的。

可以使结构体储存被其他对象拥有的数据的引用,不过这么做的话需要用上 生命周期lifetimes),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中储存一个引用而不指定生命周期,比如这样:

文件名: src/main.rs

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

编译器会抱怨它需要生命周期标识符:

error[E0106]: missing lifetime specifier
 -->
  |
2 |     username: &str,
  |               ^ expected lifetime parameter

error[E0106]: missing lifetime specifier
 -->
  |
3 |     email: &str,
  |            ^ expected lifetime parameter

第十章会讲到如何修复这个问题以便在结构体中储存引用,不过现在,我们会使用像 String 这类拥有所有权的类型来替代 &str 这样的引用以修正这个错误。