You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trpl-zh-cn/src/ch05-01-defining-structs.md

9.6 KiB

定义并实例化结构体

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

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