Rust快速入门(3) - 结构体

1. 结构体基础

结构体(struct)是一种自定义数据类型,允许我们将多个相关联的值打包并命名,形成一个有意义的组合。结构体类似于面向对象语言中对象的数据属性。

1.1 定义和实例化结构体

// 定义结构体
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

// 创建结构体实例
let user1 = User {
    email: String::from("[email protected]"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

结构体定义就像一个通用模板,而实例则使用具体数据填充这个模板,创建该类型的值。

1.2 访问和修改结构体字段

使用点表示法访问结构体字段:

let email = user1.email;

如果实例是可变的,可以使用点表示法修改字段:

let mut user1 = User { /* ... */ };
user1.email = String::from("[email protected]");

注意:结构体整体必须是可变的,Rust不允许只将某些特定字段标记为可变。

1.3 字段初始化简写语法

当参数名与字段名相同时,可以使用字段初始化简写语法:

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username,  // 等同于 username: username
        email,     // 等同于 email: email
        sign_in_count: 1,
    }
}

1.4 结构体更新语法

从另一个实例创建新实例时,可以使用结构体更新语法:

let user2 = User {
    email: String::from("[email protected]"),
    ..user1  // 其余字段使用user1的值
};

这类似于赋值操作,会发生数据移动。对于实现了Copy特性的类型(如u32),会复制值;对于像String这样的类型,所有权会被移动。

2. 结构体类型变体

2.1 元组结构体

元组结构体是类似元组的结构体,有名称但字段没有名称:

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

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

尽管字段类型相同,但ColorPoint是不同的类型。可以使用索引访问元组结构体的字段(如black.0)。

2.2 类单元结构体

没有任何字段的结构体,称为类单元结构体(unit-like struct):

struct AlwaysEqual;

let subject = AlwaysEqual;

这类结构体在需要在某些类型上实现特征(trait)但不需要存储数据时很有用。

3. 结构体数据的所有权

结构体定义时可以选择存储所有权类型(如String)或引用(如&str):

struct User {
    username: String,  // 拥有所有权
    email: String,     // 拥有所有权
    // ...
}

选择所有权类型意味着结构体实例拥有其所有数据,且数据在实例有效期间保持有效。

如果需要在结构体中存储引用,必须使用生命周期(lifetime):

struct User<'a> {
    username: &'a str,
    email: &'a str,
    // ...
}

生命周期确保结构体引用的数据在结构体有效时也保持有效。

4. 使用结构体的示例程序

以下是一个使用结构体计算矩形面积的例子,展示了结构体如何提高代码的清晰度和可维护性:

4.1 使用单独变量

fn main() {
    let width = 30;
    let height = 50;
    println!("面积是:{}", area(width, height));
}

fn area(width: u32, height: u32) -> u32 {
    width * height
}

4.2 使用元组重构

fn main() {
    let rect = (30, 50);
    println!("面积是:{}", area(rect));
}

fn area(dimensions: (u32, u32)) -> u32 {
    dimensions.0 * dimensions.1
}

4.3 使用结构体重构

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };
    println!("面积是:{}", area(&rect));
}

fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}

使用结构体的优势在于:通过给数据命名增加了含义,参数之间的关系更加明确,代码更易读。

4.4 通过派生特征添加实用功能

要打印结构体实例进行调试,可以添加Debug特征:

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

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };
    println!("rect is {:?}", rect);  // 使用{:?}格式化
    println!("rect is {:#?}", rect); // 使用{:#?}美化打印
}

调试信息可以使用println!宏的{:?}{:#?}占位符,或者使用dbg!宏。

5. 方法语法

方法与函数类似,但它们在结构体(或枚举、特征对象)的上下文中定义,并且第一个参数始终是self

5.1 定义方法

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

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

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };
    println!("面积是:{}", rect.area());
}

使用impl(实现)块将方法与结构体关联。第一个参数&self表示方法借用了结构体实例。

方法可以:

  • 借用实例不可变引用:&self
  • 借用实例可变引用:&mut self
  • 获取实例所有权:self(较少使用)

5.2 带有更多参数的方法

方法可以有多个参数:

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

5.3 关联函数

impl块中定义的没有self参数的函数称为关联函数(associated functions):

impl Rectangle {
    // 创建正方形的关联函数
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let sq = Rectangle::square(3);
}

关联函数通常用作构造函数,使用:: 语法调用。

5.4 多个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
    }
}

总结

结构体让你创建对领域有意义的自定义类型,将相关数据连接在一起并命名各个部分,使代码更加清晰。通过impl块,可以定义与类型关联的函数,其中方法是一种关联函数,它们指定结构体实例的行为。

结构体为Rust提供了强大的类型系统支持,与枚举一起,成为创建自定义类型的核心工具。


好好学习,天天向上