Rust 入门:编译器是你最严格的朋友 书架

第 07 章 / 共 12 章

第七章 · 结构体与方法

把散装数据捆成一个「东西」

地基打完,开始盖房子。写真实程序的第一需求是:把几个相关的数据捆在一起当一个东西用。比如一个用户,有名字、邮箱、年龄三个字段。Rust 里这个「捆」叫结构体(struct):

struct User {
    name: String,
    email: String,
    age: u32,
}

声明一个结构体,等于向编译器报备了一种新类型,从此它和 i32、String 平起平坐。造一个实例、访问字段,都很直白:

let u = User {
    name: String::from("阿蟹"),
    email: String::from("[email protected]"),
    age: 27,
};
println!("{}", u.name);   // 点号取字段

字段的所有权规则一个不少:u.name 是个 String,把它 move 走之后,u 就残缺了(不能再整体使用,但没被移走的字段还能用)。所有权不是只管单个变量,它管的是每一个值,包括躲在结构体肚子里的。

方法:让数据带上自己的操作

光有数据还不够,我们想写 u.is_adult() 这样的调用。Rust 的做法是用 impl 块(implementation,「实现块」)把函数挂到类型上:

impl User {
    fn is_adult(&self) -> bool {
        self.age >= 18
    }

    fn birthday(&mut self) {
        self.age += 1;
    }
}

第一个参数是self,指「调用这个方法的那个实例自己」。它的三种写法恰好就是借用规则的现场教学:

调用 u.is_adult() 时你不用手写引用,Rust 自动帮你加上——这叫「自动引用」,是个贴心的语法糖。

对比一下就明白 Rust 的克制:Java 里方法写在 class 里面,数据和操作焊死在一起;Rust 里 struct 只管数据,impl 只管操作,两者可以分开写、甚至分文件写。效果一样,但结构更松——后面讲 trait 时你会看到这个「松」留出了多大的余地。

关联函数:不挂在实例上的「静态方法」

impl 块里还可以写不收 self 的函数,叫关联函数(associated function)——大白话是「挂在类型上、但不属于任何实例的函数」,类似别的语言里的静态方法。最经典的用途是构造函数:

impl User {
    fn new(name: &str, age: u32) -> User {
        User {
            name: String::from(name),
            email: String::new(),
            age,
        }
    }
}

let u = User::new("阿蟹", 27);   // 用 :: 而不是 . 调用

调用时用 ::(从类型出发)而不是 .(从实例出发),一眼就能看出「这是在造东西,不是在用东西」。你其实已经见过它了:String::from(...) 就是 String 的关联函数。

注意 new 只是社区约定俗成的名字,不是关键字。Rust 没有传统意义的构造函数——一个关联函数返回一个实例,仅此而已。没有魔法,只有约定。

元组结构体与单元结构体:两个边角料

顺手认识两个变体,偶尔很顺手。字段懒得起名时,用元组结构体,按位置访问:

struct Color(u8, u8, u8);
let rust_orange = Color(183, 65, 14);
println!("{}", rust_orange.0);   // 183

它比裸元组 (u8, u8, u8) 强在是个独立类型——Color 和 Point 哪怕字段一模一样也互不兼容,传参时不会张冠李戴。还有一种连字段都没有的单元结构体 struct Marker;,纯粹当类型标记用,后面讲 trait 时有用武之地。

结构体把「一个东西是什么」说清楚了。但程序里还有另一种同样常见的形状:一个东西是几种可能之一——一个网络请求要么成功要么失败,一个值要么存在要么不存在。这种「之一」用结构体表达很别扭,它是下一章枚举的主场。

Rust 入门:编译器是你最严格的朋友 · 王建硕
费曼式写法 · 由多个 AI 代理撰写与互相审校