讲解
结构体(struct)把相关的命名字段组合成一个自定义类型,类似其他语言里的「记录」或「纯数据对象」。定义用 struct 关键字加花括号列出字段名和类型;创建实例时给每个字段赋值(顺序无所谓),用点号访问字段。实例整体可变或不可变——Rust 不支持只把某几个字段标成 mut,可变性属于整个实例。
两个高频语法糖。字段初始化简写:当局部变量名和字段名相同时,build_user 里写 name 而不是 name: name。结构体更新语法:基于已有实例创建新实例时,..base 表示「其余字段从 base 搬过来」——注意这是移动语义,base 里 String 类型的字段被搬走后,base 就不能再整体使用了(Copy 类型的字段仍可读)。
结构体还有两种变体。元组结构体 struct Color(i32, i32, i32) 给「几个值的组合」起名字,字段靠位置区分,适合坐标、颜色这类语义明确的短组合;单元结构体 struct Marker 没有任何字段,常用于只承载行为(实现 Trait)的类型。另外提醒:想让结构体实例能被 println!("{:?}") 调试打印,在定义上加 #[derive(Debug)] 属性即可,编译器会自动生成实现。
示例
定义结构体、字段简写与 Debug 打印:
#[derive(Debug)]
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn build_user(username: String, email: String) -> User {
User {
username, // 字段初始化简写
email,
sign_in_count: 1,
active: true,
}
}
fn main() {
let u = build_user(String::from("小林"), String::from("lin@example.com"));
println!("{:?}", u);
assert!(u.active);
assert_eq!(u.sign_in_count, 1);
assert_eq!(u.username, "小林");
}
更新语法与元组结构体:
#[derive(Debug)]
struct Config {
host: String,
port: u16,
retries: u32,
}
// 元组结构体:有名字的元组
struct Color(u8, u8, u8);
fn main() {
let default = Config {
host: String::from("localhost"),
port: 8080,
retries: 3,
};
// 更新语法:port 改了,其余从 default 移动过来
let prod = Config {
port: 443,
..default
};
// default 的 host 字段已被移动,default 整体不能再用
assert_eq!(prod.host, "localhost");
assert_eq!(prod.port, 443);
assert_eq!(prod.retries, 3);
let black = Color(0, 0, 0);
println!("黑色 RGB:({}, {}, {})", black.0, black.1, black.2);
assert_eq!(black.0, 0);
}
常见坑
- 想只 mut 一个字段:可变性属于整个实例,let mut u 之后所有字段都能改;没有字段级可变性语法。
- 更新语法后还用旧实例:..base 会移动 base 的非 Copy 字段,之后 println!("{:?}", base) 编译报错——这是所有权规则在起作用。
- 在结构体里直接存引用:struct S { s: &str } 需要生命周期标注(第 21 章),入门阶段先用 String 让结构体拥有数据。
- 打印结构体报错:{:?} 要求实现 Debug;忘了加 #[derive(Debug)] 会收到编译器提示,照着加即可。
小结
struct 组合命名字段;字段简写和 ..base 更新语法让构造更顺;元组结构体给组合起名;可变性整体生效;derive(Debug) 解锁调试打印。数据有了,下一章给数据配行为:方法。