文章

Rust 结构体

Rust

Rust 结构体

Rust 结构体

结构体

struct 是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。

定义和实例化

结构体的每一部分都可以是不同类型,不同于元组,结构体需要命名各部分数据以便能清楚的表明其意义,有了这些名字,结构体比元组更加灵活,不需要依赖顺序来指定或访问实例中的值。

定义结构体,需要使用 struct 关键字,并给整个结构体提供一个名字。接着需要在结构体中定义每一部分数据的名字和类型,这些数据称为字段(field)。

struct User {
  username: String,
  age: u32,
  gender: String,
  is_active: bool,
}

定义好结构体后,通过为每个字段指定具体值来创建这个结构体的实例。实例中字段的顺序不需要和它们在结构体中声明的顺序一致

fn main() {
  let mut user = User {
    username: String::from("Licodeao"),
    age: 21,
    gender: String::from("Male"),
    is_active: true,
  };
}

可以使用点号从结构体中获取特定的值,修改原有值时,该结构体实例必须是可变的,不能只是某个字段是可变的,要整体可变。

struct User {
  username: String,
  age: u32,
  gender: String,
  is_active: bool,
}
 
fn main() {
  let mut user = User {
    username: String::from("Licodeao"),
    age: 21,
    gender: String::from("Male"),
    is_active: true,
  };
 
  user.username = String::from("licodeao@gmail.com");
  user.age = 30;
  user.gender = String::from("Unknown");
  user.is_active = false;
}

返回结构体

fn main() {
  fn return_user(username: String, gender: String) -> User {
    User {
      username: username,
      age: 21,
      gender: gender,
      is_active: true,
    }
  }
 
  let return_user = return_user(String::from("Licodeao"), String::from("Male"));
 
  println!("Username: {}", return_user.username);
}

字段初始化简写语法(field in shorthand)

(与 ES6 中对象增强中的属性增强简直一模一样…)

fn main() {
  fn return_user(username: String, gender: String) -> User {
    User {
      username,
      age: 21,
      gender,
      is_active: true,
    }
  }
 
  let return_user = return_user(String::from("Licodeao"), String::from("Male"));
 
  println!("Username: {}", return_user.username);
}

创建新实例

通过结构体创建新的实例,除了使用旧实例的大部分值但是改变其部分值来创建,还可以通过结构体更新语法(struct update syntax)

(与 ES6 中的扩展运算符简直一模一样,只是少了个点…)

.. 语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值。

struct User {
  username: String,
  age: u32,
  gender: String,
  is_active: bool,
}
 
fn main() {
  let user = User {
    username: String::from("Licodeao"),
    age: 21,
    gender: String::from("Male"),
    is_active: true,
  };
 
  let user2 = User {
    username: String::from("licodeao@gmail.com"),
    // 结构体更新语法
    ..user
  };
 
  println!("User2: {}", user2.username);
}

⚠️:结构更新语法就像带有 = 的赋值,因为它移动了数据,总体上说我们在创建 user2 后不能就再使用 user1

没有命名的元组结构体

定义与元组类似的结构体,称为元组结构体。元组结构体有结构体名称,但是没有具体的字段名,只有字段类型。

struct Color(u32, u32, u32);
struct Point(i32, i32, i32);
 
fn main() {
  let red = Color(255, 255, 255);
  let origin = Point(100, -100, -255);
}

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

定义一个没有任何字段的结构体,称为类单元结构体(unit-like structs),因为它类似于 ()

类单元结构体常常在你想要在某个类型上实现 trait ,但不需要在类型中存储数据时发挥作用。

struct UnitLikeStruct;
 
fn main() {
  let unit_like_struct = UnitLikeStruct;
}

结构体中的所有权

当结构体中存储被其他对象拥有的数据的引用时,需要用上生命周期(lifetimes)。

如果尝试在结构体中存储一个引用而不指定生命周期,这将会是无效的。

struct User {
    active: bool,
    username: &str,
    email: &str,
    sign_in_count: u64,
}
 
fn main() {
    let user1 = User {
        active: true,
        username: "LICODEAO",
        email: "licodeao@gmail.com",
        sign_in_count: 1,
    };
}

会出现以下错误:

 --> src/main.rs:3:13
  |
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:10
  |
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,
  |

编译器会提示它需要生命周期标识符…

打印结构体

struct Rectangle {
  width: u32,
  height: u32,
}
 
fn main() {
  let rect1 = Rectangle {
      width: 30,
      height: 50,
  };
 
  println!("rect1 is {}", rect1);
}

运行上方代码后,会出现以下问题:

error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`

println! 宏能处理很多类型的格式,不过 {} 告诉 println! 宏使用被称为 Display 的格式,它像用户展示 1 或其他任何基本类型的唯一方式。不过对于结构体,println! 并没有提供一个 Display

{} 中加入 :? 提示符告诉 println! 使用 Debug 的输出格式,Debug 是一个 trait,必须显式地为结构体标注要使用 Debug

// 增加外部属性来派生 Debug trait
#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}
 
fn main() {
  let rect1 = Rectangle {
      width: 30,
      height: 50,
  };
 
  println!("rect1 is {:?}", rect1); // rect1 is Rectangle { width: 30, height: 50 }
}

使用 {:#?} 替换 {:?} ,能够看到更漂亮和易读的输出

#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}
 
fn main() {
  let rect1 = Rectangle {
      width: 30,
      height: 50,
  };
 
  println!("rect1 is {:#?}", rect1);
}
rect1 is Rectangle {
    width: 30,
    height: 50,
}

方法语法

方法与函数类似:都可以使用 fn 关键字和名称声明,并且可以拥有参数和返回值,同时包含在某处调用时会执行的代码。

方法与函数不同的是:方法在结构体的上下文、枚举、trait 对象中被定义,并且它们第一个参数永远是 self ,它代表调用该方法的结构体实例。

定义方法

#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}
 
impl Rectangle {
  // 方法语法,self 指向 rect1这个结构体实例
  fn area(&self) -> u32 {
      self.width * self.height
  }
}
 
fn main() {
  let rect1 = Rectangle {
      width: 30,
      height: 50,
  };
 
  println!("rect1 is {:?}", rect1.area());
}

使用 &self 来替代 rectangle: &Rectangle&self 实际上是 self: &Self 的缩写。

在一个 impl 块中,Self 类型是 impl 块的类型的别名。方法的第一个参数必须有一个名为 selfSelf 类型的参数。

方法可以与字段同名

#[derive(Debug)]
struct Rectangle {
  width: u32,
  height: u32,
}
 
impl Rectangle {
  fn width(&self) -> bool {
    self.width > 0
  }
}
 
fn main() {
  let rect1 = Rectangle {
      width: 30,
      height: 50,
  };
 
  if rect1.width() {
    println!("rect1 width is {:?}", rect1.width);
  }
}

当在 rect1.width 后面加上括号时,Rust 知道指的是方法 width;当不使用圆括号时,Rust 知道指的是字段 width

与字段同名的方法将被定义为只返回字段中的值,而不做其他事情,这样的方法被称为 getters

关联函数

所有在 impl 块中定义的函数被称为关联函数,因为它们与 impl 后面命名的类型相关。

可以定义不以 self 为第一个参数的关联函数,因此它不是一个方法,因为它并不作用于一个结构体实例。

不是方法的关联函数经常被用作返回一个结构体新实例的构造函数

impl Rectangle {
  fn square(size: u32) -> Self {
    Self {
      width: size,
      height: size,
    }
  }
}

关键字 Self 在函数的返回类型中代指在 impl 关键字后出现的类型,这里指的是 Rectangle ,即 Self 指的是 Rectangle

使用结构体和 :: 来调用这个关联函数,如 Rectangle::square(6)

多个 impl

每个结构体都允许有多个 impl 块。

每个方法有其自己的 impl 块。

impl Rectangle {
  fn area(&self) -> u32 {
    self.width * self.height
  }
}
 
impl Rectangle {
  fn can_hold(&self, other: &Rectangle) -> bool {
    self.width > other.width && self.height > other.height
  }
}

使用结构体能够方便地组装数据,使得代码更加清晰,但结构体并不是创建自定义类型的唯一方法,还可以使用枚举。