定义并实例化结构体

结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。
  1. struct User {
  2. username: String,
  3. email: String,
  4. sign_in_count: u64,
  5. active: bool,
  6. }
  7. let user1 = User {
  8. email: String::from("someone@example.com"),
  9. username: String::from("someusername123"),
  10. active: true,
  11. sign_in_count: 1,
  12. };
  13. fn build_user(email: String, username: String) -> User {
  14. User {
  15. email: email,
  16. username: username,
  17. active: true,
  18. sign_in_count: 1,
  19. }
  20. }
  21. }
为函数参数起与结构体字段相同的名字是可以理解的,但是不得不重复 emailusername 字段名称与变量有些啰嗦。

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

  1. fn build_user(email: String, username: String) -> User {
  2. User {
  3. email,
  4. username,
  5. active: true,
  6. sign_in_count: 1,
  7. }
  8. }
这里我们创建了一个新的 User 结构体实例,它有一个叫做 email 的字段。我们想要将 email 字段的值设置为 build_user 函数 email 参数的值。因为 email 字段与 email 参数有着相同的名称,则只需编写 email 而不是 email: email

使用结构体更新语法从其他实例创建实例

结构体更新语法struct update syntax

  1. // old
  2. let user2 = User {
  3. email: String::from("another@example.com"),
  4. username: String::from("anotherusername567"),
  5. active: user1.active,
  6. sign_in_count: user1.sign_in_count,
  7. };
  8. // new
  9. let user2 = User {
  10. email: String::from("another@example.com"),
  11. username: String::from("anotherusername567"),
  12. ..user1
  13. };
.. 语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值。

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

也可以定义与元组类似的结构体,称为 元组结构体tuple structs 当你想给整个元组取一个名字,并使元组成为与其他元组不同的类型时,元组结构体是很有用的,这时像常规结构体那样为每个字段命名就显得多余和形式化了。
  1. struct Color(i32, i32, i32);
  2. struct Point(i32, i32, i32);
  3. let black = Color(0, 0, 0);
  4. let origin = Point(0, 0, 0);
注意 blackorigin 值的类型不同,因为它们是不同的元组结构体的实例。你定义的每一个结构体有其自己的类型,即使结构体中的字段有着相同的类型。例如,一个获取 Color 类型参数的函数不能接受 Point 作为参数,即便这两个类型都由三个 i32 值组成。 在其他方面,元组结构体实例类似于元组:可以将其解构为单独的部分,也可以使用 . 后跟索引来访问单独的值,等等。

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

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

结构体数据的所有权

  1. struct User {
  2. username: &str,
  3. email: &str,
  4. sign_in_count: u64,
  5. active: bool,
  6. }
  7. fn main() {
  8. let user1 = User {
  9. email: "someone@example.com",
  10. username: "someusername123",
  11. active: true,
  12. sign_in_count: 1,
  13. };
  14. }
  15. // 报错 编译器会抱怨它需要生命周期标识符:

一个使用结构体的示例程序

http://120.78.128.153/rustbook/ch05-02-example-structs.html

  1. fn main() {
  2. let width1 = 30;
  3. let height1 = 50;
  4. println!(
  5. "The area of the rectangle is {} square pixels.",
  6. area(width1, height1)
  7. );
  8. }
  9. fn area(width: u32, height: u32) -> u32 {
  10. width * height
  11. }

使用元组重构

  1. fn main() {
  2. let rect1 = (30, 50);
  3. println!(
  4. "The area of the rectangle is {} square pixels.",
  5. area(rect1)
  6. );
  7. }
  8. fn area(dimensions: (u32, u32)) -> u32 {
  9. dimensions.0 * dimensions.1
  10. }

使用结构体重构:赋予更多意义

  1. struct Rectangle {
  2. width: u32,
  3. height: u32,
  4. }
  5. fn main() {
  6. let rect1 = Rectangle { width: 30, height: 50 };
  7. println!(
  8. "The area of the rectangle is {} square pixels.",
  9. area(&rect1)
  10. );
  11. }
  12. fn area(rectangle: &Rectangle) -> u32 {
  13. rectangle.width * rectangle.height
  14. }

通过派生 trait 增加实用功能

  1. struct Rectangle {
  2. width: u32,
  3. height: u32,
  4. }
  5. fn main() {
  6. let rect1 = Rectangle { width: 30, height: 50 };
  7. println!("rect1 is {}", rect1);
  8. }
  9. // error `Rectangle` doesn't implement `std::fmt::Display`
  10. // = help: the trait `std::fmt::Display` is not implemented for `Rectangle`
  11. // = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
  1. struct Rectangle {
  2. width: u32,
  3. height: u32,
  4. }
  5. fn main() {
  6. let rect1 = Rectangle { width: 30, height: 50 };
  7. println!("rect1 is {:?}", rect1);
  8. }
  9. // error[E0277]: `Rectangle` doesn't implement `std::fmt::Debug`
  10. // = help: the trait `std::fmt::Debug` is not implemented for `Rectangle`
  11. // = note: add `#[derive(Debug)]` or manually implement `std::fmt::Debug`
  1. #[derive(Debug)]
  2. struct Rectangle {
  3. width: u32,
  4. height: u32,
  5. }
  6. fn main() {
  7. let rect1 = Rectangle { width: 30, height: 50 };
  8. println!("rect1 is {:?}", rect1);
  9. }
  10. // rect1 is Rectangle { width: 30, height: 50 }
当我们有一个更大的结构体时,能有更易读一点的输出就好了,为此可以使用 {:#?} 替换 println! 字符串中的 {:?} Rust 为我们提供了很多可以通过 derive 注解来使用的 trait,他们可以为我们的自定义类型增加实用的行为。

方法语法

方法 与函数类似:它们使用 fn 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义,并且它们第一个参数总是 self,它代表调用该方法的结构体实例。

定义方法

  1. #[derive(Debug)]
  2. struct Rectangle {
  3. width: u32,
  4. height: u32,
  5. }
  6. impl Rectangle {
  7. fn area(&self) -> u32 {
  8. self.width * self.height
  9. }
  10. }
  11. fn main() {
  12. let rect1 = Rectangle { width: 30, height: 50 };
  13. println!(
  14. "The area of the rectangle is {} square pixels.",
  15. rect1.area()
  16. );
  17. }
implimplementation 的缩写 使用 &self 来替代 rectangle: &Rectangle,因为该方法位于 impl Rectangle 上下文中所以 Rust 知道 self 的类型是 Rectangle

-> 运算符到哪去了?

在 C/C++ 语言中,有两个不同的运算符来调用方法:. 直接在对象上调用方法,而 -> 在一个对象的指针上调用方法,这时需要先解引用(dereference)指针。换句话说,如果 object 是一个指针,那么 object->something() 就像 (*object).something() 一样。 Rust 并没有一个与 -> 等效的运算符;相反,Rust 有一个叫 自动引用和解引用automatic referencing and dereferencing)的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。 他是这样工作的:当使用 object.something() 调用方法时,Rust 会自动为 object 添加 &&mut* 以便使 object 与方法签名匹配。也就是说,这些代码是等价的:
  1. p1.distance(&p2);
  2. (&p1).distance(&p2);

带有更多参数的方法

  1. fn main() {
  2. let rect1 = Rectangle { width: 30, height: 50 };
  3. let rect2 = Rectangle { width: 10, height: 40 };
  4. let rect3 = Rectangle { width: 60, height: 45 };
  5. println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
  6. println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
  7. }
  8. impl Rectangle {
  9. fn area(&self) -> u32 {
  10. self.width * self.height
  11. }
  12. fn can_hold(&self, other: &Rectangle) -> bool {
  13. self.width > other.width && self.height > other.height
  14. }
  15. }

关联函数

impl 块的另一个有用的功能是:允许在 impl 块中定义 self 作为参数的函数。这被称为 关联函数associated functions),因为它们与结构体相关联。它们仍是函数而不是方法,因为它们并不作用于一个结构体的实例。 String::from
  1. impl Rectangle {
  2. fn square(size: u32) -> Rectangle {
  3. Rectangle { width: size, height: size }
  4. }
  5. }
使用结构体名和 :: 语法来调用这个关联函数:比如 let sq = Rectangle::square(3);。这个方法位于结构体的命名空间中::: 语法用于关联函数和模块创建的命名空间。

多个 impl 块

每个结构体都允许拥有多个 impl 块。
  1. impl Rectangle {
  2. fn area(&self) -> u32 {
  3. self.width * self.height
  4. }
  5. }
  6. impl Rectangle {
  7. fn can_hold(&self, other: &Rectangle) -> bool {
  8. self.width > other.width && self.height > other.height
  9. }
  10. }
这里没有理由将这些方法分散在多个 impl 块中,不过这是有效的语法。 结构体让你可以创建出在你的领域中有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。方法允许为结构体实例指定行为,而关联函数将特定功能置于结构体的命名空间中并且无需一个实例。