首页 / Rust 入门教程 / 定义结构体

Rust 入门教程

定义结构体

本教程共 78 篇 · 第 26 篇 · 更新于 2026-08-08 · 约 10 分钟阅读

RustRust 入门教程struct结构体字段更新语法元组结构体

本节目标:用 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

user2let 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,就行了。boolu32 这类 Copy 字段是复制,原实例还能用;String 这类是非 Copy 字段,会移动,原实例那部分字段失效。

26-6 字段初始化简写

当变量名和字段名一样时,可以简写,少写一遍:

fn build_user(username: String, email: String) -> User {
    User {
        username, // 等价于 username: username,
        email,    // 等价于 email: email,
        age: 18,
        active: true,
    }
}

函数参数 usernameemail 和字段同名,直接写名字即可,编译器懂你的意思。这个小语法让构造代码清爽不少,尤其是字段多的时候,少写一堆 字段: 字段 的重复。

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,后面还会用到 ClonePartialEq 等,都是同一套机制。现阶段知道”加个 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)],省得每次想看值都要临时补。等到后面学到 Display trait,你还能自定义”给用户看的漂亮格式”;但调试阶段,Debug 完全够用。

26-13 小结

struct 把命名字段打包成自定义类型;实例用 字段: 值 创建、点号访问;.. 更新语法复用其他字段(注意移动);还有元组结构体和单元结构体两种变体。结构体是”数据容器”。下一章给它加方法,让数据能自带行为。