Rust 面向对象(完整指南)

为什么 Rust 也能实现面向对象编程?

你可能听过一句话:“Rust 没有类,所以不支持面向对象。” 这句话听起来挺有道理,但其实是个误解。Rust 确实没有传统意义上的类(class),但它通过强大的特性组合,完全可以实现与 Java 或 C++ 中类似的面向对象编程模式。

我们不妨把面向对象理解成一种设计思想:封装数据、抽象行为、支持继承与多态。Rust 虽然没有 class 关键字,却用 structtrait 和泛型等机制,实现了更安全、更灵活的面向对象表达方式。

就像盖房子,传统面向对象是用砖块和水泥搭框架;而 Rust 则是用积木拼接——每个积木都有明确用途,组合起来既稳固又可复用。这种设计让 Rust 在性能和安全性上远超传统语言。

所以,别被“没有类”吓到。接下来,我们就一步步揭开 Rust 如何实现面向对象的神秘面纱。


struct 与封装:数据的容器

在大多数面向对象语言中,类是用来封装数据和方法的。Rust 用 struct 来做这件事,它相当于一个数据容器,可以包含多个字段。

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

这段代码定义了一个名为 Rectangle 的结构体,它有两个字段:widthheight,类型都是 u32。这就像给一个矩形画框,里面固定放两个数字。

接下来,我们可以为这个结构体添加方法。Rust 用 impl 块来实现方法,就像给结构体“贴上功能标签”。

impl Rectangle {
    // 计算面积的方法
    fn area(&self) -> u32 {
        self.width * self.height
    }

    // 判断是否包含另一个矩形
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

📌 注释说明:

  • &self 表示借用当前实例,不获取所有权,避免数据被意外移动。
  • other: &Rectangle 是另一个矩形的引用,用于比较。
  • fn 定义函数,impl Rectangle 表示这些函数属于 Rectangle 类型。

使用方式如下:

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

    println!("矩形1的面积是:{}", rect1.area());
    println!("矩形1能放下矩形2吗?{}", rect1.can_hold(&rect2));
}

输出结果:

矩形1的面积是:1500
矩形1能放下矩形2吗?true

通过这种方式,Rust 完美实现了“封装”——数据和行为被绑定在一起,逻辑清晰,易于维护。


trait:行为的契约

在传统面向对象中,接口(interface)用于定义对象应该具备哪些行为。Rust 用 trait 来实现这一功能。

比如我们想让所有图形都能“绘制”或“计算面积”,就可以定义一个 Drawable trait:

trait Drawable {
    fn draw(&self);
    fn area(&self) -> f64;
}

这个 trait 声明了两个必须实现的方法:drawarea。任何类型只要实现这个 trait,就“承诺”能做这两件事。

现在我们让 Rectangle 实现它:

impl Drawable for Rectangle {
    fn draw(&self) {
        println!("正在绘制一个矩形,宽:{},高:{}", self.width, self.height);
    }

    fn area(&self) -> f64 {
        (self.width * self.height) as f64
    }
}

📌 注释说明:

  • impl Drawable for Rectangle 表示 Rectangle 类型实现了 Drawable trait。
  • 所有 trait 中声明的方法都必须被实现,否则编译会报错。

现在我们可以在函数中使用 trait 作为参数类型,实现多态效果:

fn render<T: Drawable>(item: &T) {
    item.draw();
    println!("面积:{}", item.area());
}

fn main() {
    let rect = Rectangle { width: 10, height: 20 };

    render(&rect);
}

输出:

正在绘制一个矩形,宽:10,高:20
面积:200

这里 T: Drawable 是泛型约束,表示 T 必须实现 Drawable trait。Rust 通过 trait 对象(dyn Drawable)也能实现动态分发,但需要加 Box<dyn> 语法,适合复杂场景。


继承的替代方案:组合优于继承

传统面向对象中,继承是代码复用的重要手段。但在 Rust 中,我们更推荐“组合优于继承”。

举个例子:你想做一个 Button,它有 widthheight,还能点击。如果用继承,可能会写成:

class Button extends Rectangle {
    fn click(&self) {
        println!("按钮被点击了");
    }
}

Rust 没有继承语法,但可以用结构体组合来实现:

struct Button {
    rectangle: Rectangle,  // 组合一个 Rectangle
    label: String,
}

impl Button {
    fn new(width: u32, height: u32, label: &str) -> Self {
        Button {
            rectangle: Rectangle { width, height },
            label: label.to_string(),
        }
    }

    fn click(&self) {
        println!("按钮 '{}' 被点击了", self.label);
    }

    // 可以调用 Rectangle 的方法
    fn area(&self) -> u32 {
        self.rectangle.area()
    }
}

📌 注释说明:

  • rectangle: Rectangle 表示 Button 包含一个 Rectangle 实例。
  • self.rectangle.area() 调用嵌套结构体的方法,是典型的组合复用。

这样既避免了继承的复杂性(比如菱形继承问题),又保留了代码复用的能力。


多态的实现:trait 对象与动态分发

多态是面向对象的核心特性之一。Rust 通过 trait 对象实现动态多态。

假设我们有多种图形:RectangleCircleTriangle,它们都实现了 Drawable trait。我们可以用一个统一的容器存储它们:

trait Drawable {
    fn draw(&self);
    fn area(&self) -> f64;
}

struct Rectangle { width: f64, height: f64 }
struct Circle { radius: f64 }
struct Triangle { base: f64, height: f64 }

impl Drawable for Rectangle {
    fn draw(&self) {
        println!("绘制矩形,宽:{},高:{}", self.width, self.height);
    }
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

impl Drawable for Circle {
    fn draw(&self) {
        println!("绘制圆形,半径:{}", self.radius);
    }
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

impl Drawable for Triangle {
    fn draw(&self) {
        println!("绘制三角形,底:{},高:{}", self.base, self.height);
    }
    fn area(&self) -> f64 {
        0.5 * self.base * self.height
    }
}

现在可以创建一个统一的动态数组:

fn main() {
    let shapes: Vec<Box<dyn Drawable>> = vec![
        Box::new(Rectangle { width: 10.0, height: 5.0 }),
        Box::new(Circle { radius: 3.0 }),
        Box::new(Triangle { base: 8.0, height: 6.0 }),
    ];

    for shape in shapes {
        shape.draw();
        println!("面积:{}", shape.area());
        println!("---");
    }
}

📌 注释说明:

  • Box<dyn Drawable> 是 trait 对象,表示“任意实现了 Drawable 的类型”。
  • Vec<Box<dyn Drawable>> 是一个可以存放不同图形的容器。
  • Box::new() 用于在堆上分配内存,保证类型大小可变。

输出结果:

绘制矩形,宽:10,高:5
面积:50
---
绘制圆形,半径:3
面积:28.274333882308138
---
绘制三角形,底:8,高:6
面积:24
---

这种模式完美模拟了运行时多态,是 Rust 实现“面向对象”的精髓所在。


Rust 面向对象的哲学:安全与性能并重

Rust 面向对象不是照搬 Java 或 C++ 的语法,而是从语言设计哲学出发:零成本抽象

  • 通过 impltrait,Rust 在编译期完成大部分多态调度,性能接近 C++。
  • 所有方法调用都经过严格的类型检查,避免空指针、内存泄漏等常见错误。
  • 数据所有权机制确保资源安全释放,无需垃圾回收。

比如,&self&mut self 的借用规则,让方法调用既高效又安全。你无法在调用方法期间意外修改数据,也不会出现数据竞争。

这正是 Rust 的魅力:它不牺牲性能去迎合“面向对象”的形式,而是用更先进的机制实现更强大的功能。


总结:Rust 面向对象不是“没有”,而是“不同”

Rust 没有 class,没有继承关键字,也没有运行时虚函数表。但它用 struct + trait + impl 的组合,实现了更安全、更灵活的面向对象编程。

我们总结一下关键点:

特性 Rust 实现方式 传统语言对比
封装 struct + impl class
抽象 trait interface
继承 组合(嵌套结构体) extends
多态 dyn Trait + Box 虚函数表

Rust 面向对象不是“模仿”,而是一种更现代、更安全、更高效的表达方式。它鼓励你思考“我需要什么行为”,而不是“我该继承哪个类”。

如果你正在学习 Rust,不妨放下对“类”的执念。掌握 traitimpl,你会发现 Rust 的面向对象,比你想象的更强大、更优雅。

别再问“Rust 能不能面向对象”了,它不仅能做到,而且做得更好。