定义结构体
本教程共 78 篇 · 第 26 篇 · 更新于 2026-08-08 · 约 10 分钟阅读
本节目标:用
struct把多个有名字的字段打包成一个自定义类型,掌握实例化、字段访问和..更新语法,并认识元组结构体与单元结构体两种变体。
前面元组能把多个值捆一起,但靠位置区分、可读性差。真实数据几乎都有名字语义:一个人的”姓名""年龄""邮箱”。Rust 用**结构体(struct)**表达这种”带名字的字段集合”,它是你自定义类型的核心工具。简单说,结构体就是你自己造的一种”数据类型”,把相关的信息装进一个整体。
26-1 为什么需要结构体
假设你写一个学生管理系统,要同时处理姓名、年龄、分数。不用结构体的话,你得维护三个并列的变量数组,或者把这三个值当元组传来传去——但 (String, u32, f32) 这种写法,你读代码时根本想不起来”第二个是年龄还是分数”。结构体给每个字段起名字,于是 student.age 一眼就懂。字段名是给”人”看的语义,位置是给”机器”看的顺序,结构体把语义和顺序解耦了。
26-2 定义结构体
用 struct 关键字,字段写成 名字: 类型:
struct User {
username: String,
email: String,
age: u32,
active: bool,
}
这定义了一个叫 User 的类型,含四个有名字的字段。注意字段顺序无所谓,调用方靠名字取,不靠位置——这正是比元组强的地方。
Note类型名用
PascalCase(首字母大写的驼峰,如UserProfile),字段名用snake_case。这是 Rust 的命名约定,编译器甚至会建议你遵守(warning 级别)。统一风格让全世界的 Rust 代码读起来像一个人写的。
26-3 创建实例
实例化就是写出每个字段的值:
fn main() {
let user1 = User {
username: String::from("xiaoming"),
email: String::from("xm@example.com"),
age: 18,
active: true,
};
println!("{} 的年龄是 {}", user1.username, user1.age);
}
字段值顺序可以和定义不同——你爱怎么排都行,因为靠名字识别。访问字段用点号 user1.username。有一点要记住:创建实例时每个字段都得给值,不能只填一部分,Rust 不会给你”未初始化字段”留后门。
Warning字符串字段这里用了
String(拥有型),不是&str。因为实例要自己持有这些数据,如果用&str就得处理生命周期(引用借多久),新手阶段先用String最简单,所有权清晰、实例独立。等到学了生命周期,你才会用到&str字段。
26-4 字段可变性是整个实例的
Rust 里不能只让某个字段可变、其他不变——可变性是按整个变量走的:
let mut user2 = User { /* ... */ };
user2.age = 20; // 要改字段,变量必须声明 mut
user2 用 let mut 声明,里面的字段才能改。没法只标 mut age。这点和某些语言(如给单个字段加 mutable)不同,记住可变性在变量层面,是一种”要么整块可变、要么整块不可变”的干脆态度。
26-5 结构体更新语法 ..
创建新实例、大部分字段沿用另一个实例时,用 .. 省事:
let user3 = User {
email: String::from("new@example.com"),
..user1 // 其余字段从 user1 复制/移动过来
};
..user1 表示”没显式写的字段,都照 user1 的来”。注意两点:第一,它必须写在结构体字面量的最后;第二,对于 String 这类非 Copy 字段,是移动——user1 那些字段之后就不能再用了(user1 部分失效)。这是所有权在结构体上的体现。
Tip想要”复制整份、原实例还可用”?得给结构体加
derive(Clone)后调.clone()(进阶)。现阶段理解..是移动还是复制,取决于字段类型是否 Copy,就行了。bool、u32这类 Copy 字段是复制,原实例还能用;String这类是非 Copy 字段,会移动,原实例那部分字段失效。
26-6 字段初始化简写
当变量名和字段名一样时,可以简写,少写一遍:
fn build_user(username: String, email: String) -> User {
User {
username, // 等价于 username: username,
email, // 等价于 email: email,
age: 18,
active: true,
}
}
函数参数 username、email 和字段同名,直接写名字即可,编译器懂你的意思。这个小语法让构造代码清爽不少,尤其是字段多的时候,少写一堆 字段: 字段 的重复。
26-7 元组结构体
如果字段不需要名字、只要类型,可以用”元组结构体”——像元组、又有类型名:
struct Point(i32, i32, i32);
fn main() {
let p = Point(1, 2, 3);
println!("x = {}", p.0); // 仍用 .0 .1 访问
}
Point(1,2,3) 三个 i32,常用在”坐标""颜色 RGB”这类语义靠位置、字段名反而啰嗦的场景。访问仍用 .0 .1 .2。它介于结构体和元组之间:有独立的类型名(所以类型安全,不会把 Point 和别的三个 i32 的元组搞混),但字段靠位置。
26-8 单元结构体
还有一种”没有任何字段”的结构体,叫单元结构体,纯粹用来做标记或实现 trait(后面讲):
struct AlwaysEqual;
fn main() {
let _marker = AlwaysEqual;
}
它不占什么数据,主要在某些设计模式里当一个”类型标签”用。新手先知道有这形态即可,暂时用不上,等学到 trait 你会看到它大显身手。
26-9 结构体里也能放别的类型
字段类型可以是任意已存在的类型,包括之前学的:
struct Rectangle {
width: u32,
height: u32,
}
甚至能放 Vec、其他 struct。组合这些,你就能搭出复杂的自定义数据模型。下一章我们给结构体加”方法”,让它能自己干事(比如算面积),而不仅是被动装数据。
Note关于结构体的”Debug 打印”:你现在
println!("{user1}")会报错,因为没实现显示格式。调试时可临时写成println!("{:?}", user1)并给结构体加#[derive(Debug)]。这属于常用小技巧,先留印象。
26-10 调试打印:derive(Debug)
结构体默认不能直接用 println! 打印,因为 Rust 不知道怎么显示它。调试时最常用的一招是给结构体加 #[derive(Debug)],然后用 {:?} 打印:
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 3, y: 7 };
println!("{:?}", p); // Point { x: 3, y: 7 }
}
#[derive(Debug)] 让编译器自动为 Point 生成”调试打印”能力,{:?} 触发它。这不影响程序逻辑,纯方便你看内部状态。进阶里还能用 {:#?} 打印带换行的更整齐格式,字段多时特别好用。
Tip写练习时想快速看结构体里有什么,就加
#[derive(Debug)]再println!("{:?}", 变量)。这是调试 Rust 最高频的操作之一,比单步调试来得快。但要注意Debug输出是给开发者看的,给用户看的文案要自己写(比如用方法拼字符串)。
Note
#[derive(...)]这种以#[开头、带方括号的,叫”派生宏(derive macro)“。它让编译器按约定自动实现某些 trait(接口)。除了Debug,后面还会用到Clone、PartialEq等,都是同一套机制。现阶段知道”加个 derive 就能获得某种能力”就够。
26-11 结构体与元组的取舍回顾
回看前面学的元组,它和结构体都能”把多个值打包”。怎么选?元组适合”临时、两三个、含义自明”的组合(比如函数返回两个相关值);结构体适合”有名字语义、会反复使用、带行为”的数据(比如一个用户、一张订单)。当你总在用 .0、.1 去记”第几个位置是什么”,就该换成结构体、给字段起名字。
Note结构体还能像元组那样”不写字段名”——那就是”元组结构体”(前面讲过,如
struct Point(i32, i32))。它介于两者之间:有类型名、但字段靠位置。坐标、RGB 这类”位置即语义”的场景用元组结构体最顺手。
Tip经验法则:数据会被多处使用、或字段有独立含义,用结构体;只是函数内部临时凑一组值,用元组。选错也不致命,但用对了代码可读性明显更好。
26-12 打印结构体与调试习惯
初学阶段你一定会想”把结构体打印出来看看里面是啥”。直接 println!("{}", rect); 会编译报错,因为结构体默认没有实现显示文本的能力。最快的办法是给结构体加 #[derive(Debug)],然后用 println!("{:?}", rect); 或排版更好看的 println!("{:#?}", rect); 打印。这个 Debug 派生就是前面提过的派生宏,一行换来”能调试打印”。
Tip写练习时养成习惯:凡是自定义类型,先顺手
#[derive(Debug)],省得每次想看值都要临时补。等到后面学到Displaytrait,你还能自定义”给用户看的漂亮格式”;但调试阶段,Debug完全够用。
26-13 小结
struct 把命名字段打包成自定义类型;实例用 字段: 值 创建、点号访问;.. 更新语法复用其他字段(注意移动);还有元组结构体和单元结构体两种变体。结构体是”数据容器”。下一章给它加方法,让数据能自带行为。