Skip to content

5. Rust 结构体

5.1 定义结构体

struct 关键字用于定义结构体。与 C 类似,需要为所有字段定义名称和类型。

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

注意,每个字段使用逗号分隔,即使是最后一个字段也是如此。

实例化结构体时,没有必要按照定义时的顺序指定字段的值,但需要指定字段的名称。

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

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

一旦结构体实例可变,则结构体实例中所有字段都可变。

与 ES6 类似,字段名和字段值相同时可以省略字段名。

rust
fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

还可以使用更新语法 .. 来创建一个实例,该实例的剩余字段与给定实例相同。

rust
let user2 = User {
    email: String::from("123.123.com"),
    username: String::from("123"),
    ..user1
};

还有一种特殊的结构体,称为 元组结构体(Tuple Struct)。元组结构体有名称但没有字段名。

rust
struct Color(i32, i32, i32);
let black = Color(0, 0, 0);

元组结构体的每个实例都有相同的类型,但不同的元组结构体实例之间是不兼容的。

还有一种特殊的结构体,称为 单元结构体(Unit-Like Struct)。或者称为空结构体,与 () 类似。单元结构体没有字段,用于实现某种特定的 trait。

rust
struct EmptyStruct;

一个结构体拥有所有权时,即拥有所有数据的所有权。如果结构体内存储引用但不使用生命周期就会报错。

5.2 实例:计算长方形面积

我们可以使用元组来存储长方形的宽和高。

rust
fn main() {
    let rect = (30, 50);
    println!(
        "The area of the rectangle is {} square pixels.",
        area(rect)
    );
}

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

现在我们使用结构体来实现:

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

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };
    println!("The area of the rectangle is {} square pixels.", area(&rect));
}

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

由于 area() 函数借用了 rect,所以 rect 仍然可以继续使用。

有时我们需要调试结构体中的信息,可以使用 #[derive(Debug)] 注解。

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

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };
    println!("The area of the rectangle is {} square pixels.", area(&rect));

    println!("{:?}", rect);
}

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

这将打印出 Rectangle { width: 30, height: 50 }。可以使用 {:#?} 来更好地格式化输出。

可以通过实现 std::fmt::Debug trait 来自定义调试输出。同样,可以通过实现 std::fmt::Display trait 来自定义打印输出。

5.3 方法

方法与函数类似,但是它们是在结构体上定义的。

方法在 impl 块中调用。与 Python 类似,Rust 方法的第一个参数是 self,但也可以获得其所有权,使用 self,也可以获得可变引用 &mut self

rust
#[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!("The area of the rectangle is {} square pixels.", rect.area());

    println!("{:?}", rect);
}

Rust 会在调用方法时自动引用和解引用。

下面我们使用 can_hold() 方法来确认一个长方形是否可以容纳另一个长方形。

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

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

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let rect3 = Rectangle { width: 60, height: 45 };

    println!("rect1 can hold rect2: {}", rect1.can_hold(&rect2));
    println!("rect1 can hold rect3: {}", rect1.can_hold(&rect3));
}

如果不使用 self 作为第一个参数,则此函数为关联函数,而不是方法。使用 :: 调用关联函数。

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

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

fn main() {
    let square = Rectangle::square(3);
    println!("{:?}", square);
}