使用结构体可以自定义一系列数据结构并打包在一起,然后用其定义多个符合这个结构的值。

类比 C 语言的结构体以及 TypeScript 的 Interface

结构体类似面向对象编程中的对象属性声明,使用结构体可以声明并实例化数据对象。

可被用于定义有关联的函数,这种有关联的函数称为”方法“。

结构体和枚举被用来创建新类型的代码块,以此充分利用 Rust 在编译时进行的类型检查。

声明和初始化结构体

结构体类似于元组,都包含多个相关的值,并且支持存放不同类型的值。

不同于元组的是,结构体需要给每个值进行命名,因此可以知道每个值的含义。

增加了对于值的命名也意味着结构体比元组更加灵活——无需再依赖值的顺序来访问实例内的值。

结构体声明

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

类比 C 语言结构体的声明方式

结构体实例化

实例化结构体时无需按照结构体内的变量顺序,这也意味着结构体类似于数值类型的通用”模板“

fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    }
}

结构体访问和赋值

fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    }
    user1.email = String::from("anotheremail@example.com");
}

从已有实例生成新实例

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

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

    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    }
}

对于 user2 和 user1 这种结构相同,只有部分字段更新的情况,可以使用 .. 对相同字段的值进行简写

fn main() {
    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}

.. 类似于 JS 中的剩余参数,只能用于最后

元组结构体

元组结构体使用 struct 关键字声明,可以对结构相同的元组各自进行命名,从而进行区分,用于表示不同的内容

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

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

black 和 origin 的结构相同,却是不同的自定义数据类型。比如一个函数使用 Color 作为参数类型,则不能将 Point 类型的实例作为参数传入。

类似于 TS 里的类型,即使结构相同,但属于不同的类型

方法

“方法”类似“函数”,都使用 fn 关键字进行声明,可以有参数和返回值。

方法与函数的区别在于方法只能在结构体、枚举或者特征对象内部定义,并且第一个参数始终为 self,表示方法被调用时的实例(上下文)。

类似于 python 和 java 的方法

声明方法

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

这里使用了 impl 关键字,用于表示与类型的关联的实现。&self 其实是 &self: Self 的简写,Self 表示 impl 所实现的类型。

self 既可以传递所有权(self),也可以借用不可变引用 (&self) 或借用可变引用 (&mut self),和函数的参数用法类似。

这里我们只希望读取值,而不进行值的写操作,因此使用了借用不可变引用,而非可变引用或传递所有权。

方法对比函数

之所以使用方法而非函数,是为了在多个地方能够访问 self,而无需重复作为参数传递,这样组织代码更加方便。

方法名可以和结构体内的字段同名

impl Rectangle {
    fn width(&self) -> bool {
        self.width > 0
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    if rect1.width() {
        println!("The rectangle has a nonzero width; it is {}", rect1.width);
    }
}

这里既访问了实例的 width 方法,也访问了实例的 width 属性。当使用括号访问时,表示在访问方法,没有括号时表示在访问属性。

getter

某些情况,我们希望当存在方法与属性同名时,访问方法仅仅是返回同名的属性值,这种方法称为“getter”

getter 不会被默认实现

getter 一般用于将一个结构体的属性不对外暴露(私有化),而只暴露方法(公有化)

开闭原则——对访问开放,对修改封闭