Rust 中的数据 vs 行为

发布: (2025年12月18日 GMT+8 00:40)
3 分钟阅读
原文: Dev.to

Source: Dev.to

Rust 中的结构体:建模数据

在 Rust 中,struct 仅用于表示数据。

struct Rect {
    width: f32,
    height: f32,
}

结构体本身并不定义行为。这使得数据表示保持明确且简单。

使用 impl 添加行为

Rust 通过 impl 块将行为附加到数据上。

impl Rect {
    fn area(&self) -> f32 {
        self.width * self.height
    }
}

这种方式在行为属于单一具体类型时非常理想。方法使用 &self 借用结构体,确保在不转移所有权的情况下安全访问。

Rust 中的 Trait:定义行为契约

Trait 定义跨类型的共享行为。

trait Shape {
    fn area(&self) -> f32;
}

Trait 指明 类型能做什么,而不是 如何做

impl Shape for Rect {
    fn area(&self) -> f32 {
        self.width * self.height
    }
}

这使得多个类型可以实现相同的行为,同时保持各自内部逻辑的独立。

Trait Bound 与可扩展设计

当与泛型函数一起使用时,Trait 的威力尤为强大。

fn get_area(shape: impl Shape) -> f32 {
    shape.area()
}

该函数可以接受任何实现了 Shape Trait 的类型。添加新形状无需修改已有代码,这也是为什么 Trait 被视为 Rust 可扩展性的核心工具。

Clone 与 Copy:所有权感知的复制

Rust 使用 Trait 来控制值的复制方式。

#[derive(Clone)]
struct Person {
    name: String,
    age: u32,
}

因为 String 拥有堆内存,克隆必须是显式的:

let p2 = p1.clone();

对于仅在栈上的数据,Rust 允许隐式复制:

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

这种显式的区分可以防止意外的内存错误。

Debug 与 Display:Rust 中的格式化输出

Rust 将面向开发者的输出与面向用户的输出分离。

Debug 格式化

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

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

Display 格式化

use std::fmt;

impl fmt::Display for Rect {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.width, self.height)
    }
}

println!("{}", rect);

这种分离鼓励干净的日志记录并提供更好的用户体验。

为什么这种设计有效

通过将 数据 (struct)行为 (trait) 分离,Rust 避免了继承相关的问题,促进了组合。这样可以得到更易于扩展、重构和推理的代码。

Back to Blog

相关文章

阅读更多 »