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);
}

每个结构体可以有多个 impl 块,这在组织代码时很有用。

rust
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
    }
}

5.4 结构体的高级用法

结构体更新语法

我们可以使用结构体更新语法基于现有实例创建新实例:

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

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

    let user2 = User {
        email: String::from("user2@example.com"),
        ..user1  // 使用 user1 的其余字段
    };

    // 注意:user1.username 已被移动到 user2
    // println!("{}", user1.username); // 错误
    println!("{}", user1.active); // 正确:bool 实现了 Copy
}

结构体的所有权

结构体的字段可以拥有数据,也可以存储引用。当存储引用时,需要使用生命周期标注:

rust
struct User<'a> {
    username: &'a str,
    email: &'a str,
    sign_in_count: u64,
    active: bool,
}

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

派生 trait

Rust 允许我们为结构体自动实现一些常用的 trait:

rust
#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1.clone();
    
    println!("{:?}", p1);  // Debug trait
    println!("p1 == p2: {}", p1 == p2);  // PartialEq trait
}

常用的可派生 trait:

  • Debug:允许使用 {:?} 格式化打印
  • Clone:允许显式复制值
  • Copy:允许隐式复制值(仅适用于简单类型)
  • PartialEqEq:允许比较相等性
  • PartialOrdOrd:允许比较大小
  • Hash:允许哈希

结构体方法的链式调用

可以设计方法返回 self 来支持链式调用:

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

impl Rectangle {
    fn new(width: u32, height: u32) -> Self {
        Rectangle { width, height }
    }
    
    fn set_width(mut self, width: u32) -> Self {
        self.width = width;
        self
    }
    
    fn set_height(mut self, height: u32) -> Self {
        self.height = height;
        self
    }
    
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect = Rectangle::new(10, 20)
        .set_width(30)
        .set_height(40);
    
    println!("Area: {}", rect.area());
}

注意:这种模式会获取所有权,如果想保持引用,需要返回 &mut self

rust
impl Rectangle {
    fn set_width(&mut self, width: u32) -> &mut Self {
        self.width = width;
        self
    }
    
    fn set_height(&mut self, height: u32) -> &mut Self {
        self.height = height;
        self
    }
}

fn main() {
    let mut rect = Rectangle::new(10, 20);
    rect.set_width(30)
        .set_height(40);
    
    println!("Area: {}", rect.area());
}

示例:构建一个简单的点和矩形系统

rust
#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn new(x: i32, y: i32) -> Self {
        Point { x, y }
    }
    
    fn distance(&self, other: &Point) -> f64 {
        let dx = (self.x - other.x) as f64;
        let dy = (self.y - other.y) as f64;
        (dx * dx + dy * dy).sqrt()
    }
}

#[derive(Debug)]
struct Rectangle {
    top_left: Point,
    bottom_right: Point,
}

impl Rectangle {
    fn new(top_left: Point, bottom_right: Point) -> Self {
        Rectangle { top_left, bottom_right }
    }
    
    fn width(&self) -> i32 {
        (self.bottom_right.x - self.top_left.x).abs()
    }
    
    fn height(&self) -> i32 {
        (self.bottom_right.y - self.top_left.y).abs()
    }
    
    fn area(&self) -> i32 {
        self.width() * self.height()
    }
    
    fn contains(&self, point: &Point) -> bool {
        point.x >= self.top_left.x
            && point.x <= self.bottom_right.x
            && point.y >= self.top_left.y
            && point.y <= self.bottom_right.y
    }
}

fn main() {
    let p1 = Point::new(0, 0);
    let p2 = Point::new(10, 10);
    
    println!("点之间的距离: {:.2}", p1.distance(&p2));
    
    let rect = Rectangle::new(p1, p2);
    println!("矩形: {:?}", rect);
    println!("宽度: {}", rect.width());
    println!("高度: {}", rect.height());
    println!("面积: {}", rect.area());
    
    let p3 = Point::new(5, 5);
    println!("点 {:?} 在矩形内: {}", p3, rect.contains(&p3));
}

通过这些示例,我们可以看到结构体是如何组织和封装相关数据和行为的。结构体是 Rust 中构建复杂程序的基础工具之一。