第 5 章:class 在哪里?

这是每个 Java 程序员学 Rust 问的第一个问题。

答案很残酷:Rust 没有 class。

但先别慌,Rust 不是退步,而是进化。它要用一种更优雅、更安全、更灵活的方式来重新定义"对象"。

你准备好抛弃那些陈旧的面向对象思维了吗?

思维革命:数据与行为的分离

Java 教会了我们什么?数据和行为必须绑定在一个 class 里。

class User {
    private String name;
    private int age;

    public String getName() { return name; }
    public void setAge(int age) { this.age = age; }
}

这是上世纪的设计思想!

Rust 说:为什么要把数据和行为强制绑定?为什么不能分开定义?

  • 数据就是数据 - 用 struct 定义
  • 行为就是行为 - 用 impl 实现

这种分离不是缺点,是优点!

struct:纯粹的数据容器

停止写那些臃肿的 class,从纯粹的数据开始:

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

看到了吗?没有方法,没有构造函数,没有 getter/setter 的噪音。

就是纯粹的数据定义。简洁到令人发指。

创建实例?一样简单:

let user = User {
    email: String::from("[email protected]"),
    username: String::from("someuser"),
    active: true,
    sign_in_count: 1,
};

你甚至可以用字段简写:

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

这才是现代语言应有的简洁性。

impl:赋予数据灵魂

数据有了,行为呢?这就是 impl 的威力所在。

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

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

看到了吗?数据定义和行为实现完全分离。

这种分离带来什么好处?

  1. 数据结构更清晰 - 一眼就能看出有哪些字段
  2. 行为更聚焦 - 每个 impl 块都有明确的职责
  3. 扩展更灵活 - 可以在不同地方为同一个 struct 添加方法

这就是现代软件设计的"组合优于继承"原则的体现。

self 家族:比 this 更明确

Java 的 this 是隐式的,你永远不知道一个方法会不会修改对象状态。

Rust 让一切都明确化:

&self:只读借用

impl Rectangle {
    fn area(&self) -> u32 {  // 我只是看看,不会改变你
        self.width * self.height
    }
}

&self 告诉你:这个方法是只读的,不会修改对象。

&mut self:可写借用

impl Rectangle {
    fn scale(&mut self, factor: u32) {  // 我要修改你了
        self.width *= factor;
        self.height *= factor;
    }
}

&mut self 明确表示:这个方法会修改对象状态。

self:获取所有权

impl Rectangle {
    fn into_square(self) -> Rectangle {  // 我要消费掉你
        let size = std::cmp::max(self.width, self.height);
        Rectangle {
            width: size,
            height: size,
        }
    }
}

self 表示:调用这个方法后,原对象就不存在了。

在 Java 里,你得靠文档和命名约定来表达这些语义。在 Rust 里,类型系统强制你明确表达意图。

这就是类型安全的威力。

关联函数:static 方法的进化

Java 的 static 方法?Rust 叫关联函数,而且更优雅:

impl Rectangle {
    fn new(width: u32, height: u32) -> Self {  // Self = Rectangle
        Self { width, height }
    }

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

// 调用方式
let rect = Rectangle::new(10, 20);
let square = Rectangle::square(15);

:: 调用关联函数,用 . 调用实例方法。语法就告诉你区别。

多个 impl 块:模块化的极致

最强大的特性来了:你可以为同一个 struct 写多个 impl 块!

// 基础功能
impl Rectangle {
    fn new(width: u32, height: u32) -> Self {
        Self { width, height }
    }

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

// 几何操作
impl Rectangle {
    fn scale(&mut self, factor: u32) {
        self.width *= factor;
        self.height *= factor;
    }

    fn rotate(&mut self) {
        std::mem::swap(&mut self.width, &mut self.height);
    }
}

// 比较操作
impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

这种模块化是 Java class 永远做不到的。

在 Java 里,所有方法都必须塞在一个 class 里。Rust 让你按功能分组,让代码组织更清晰。

对比:Java vs Rust

Java 方式 Rust 方式
数据和行为强制绑定在 class 里 数据(struct)和行为(impl)分离
this 隐式,语义不明确 &self/&mut self/self 明确语义
一个 class 包含所有方法 多个 impl 块模块化组织
继承解决代码复用 组合和 trait 解决代码复用

哪种更现代?哪种更安全?哪种更灵活?

答案显而易见。

写在最后:面向对象的重新定义

Java 的面向对象是 20 世纪的产物,它的核心思想是"封装":把数据和操作数据的方法绑定在一起。

但这种绑定是强制的、僵硬的。

Rust 的方式是 21 世纪的思维:

  • 数据就是数据 - 纯粹、简洁、易于理解
  • 行为就是行为 - 按需组织、模块化、可扩展
  • 类型系统保证安全 - 编译期检查、明确语义、零运行时成本

这不是没有面向对象,这是面向对象的进化。

当你习惯了这种方式,你会发现:

  • 代码结构更清晰了
  • 扩展功能更容易了
  • 理解别人的代码更快了
  • 重构的信心更足了

这就是现代编程语言的魅力。

我们已经学会了如何组织数据和行为。但还有一个重要问题:如何管理这些数据的生命周期?

下一章我们要面对 Rust 最核心、也是最"可怕"的概念:所有权系统

准备好接受终极挑战了吗?因为理解了所有权,你就真正理解了 Rust 的精髓。