这是每个 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
}
}
看到了吗?数据定义和行为实现完全分离。
这种分离带来什么好处?
- 数据结构更清晰 - 一眼就能看出有哪些字段
- 行为更聚焦 - 每个
impl
块都有明确的职责 - 扩展更灵活 - 可以在不同地方为同一个 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 的精髓。