本节摘要:讲命名结构体、元组结构体与单元结构体三种形态,演示用 impl 块挂方法、关联函数当构造器,并解释"移动一个结构体就是移动整袋证据"的所有权含义。读完你能用结构体把领域概念建成类型。
struct Case { // 命名结构体:主形态 no: u32, title: String, closed: bool, } struct Point(f64, f64); // 元组结构体:字段靠位置 struct Marker; // 单元结构体:零字段,当类型级标记
命名结构体覆盖九成场景;元组结构体适合"就是那个东西包一层"的场合(如毫米 NewType);单元结构体常用于类型状态编程与 trait 标记。
impl Case { fn new(no: u32, title: String) -> Self { // 关联函数,无 self Case { no, title, closed: false } // 字段简写:同名省略 } fn close(&mut self) { self.closed = true; } // 可变借用,就地修改 fn label(&self) -> String { // 只读借用 format!("#{} {}", self.no, self.title) } } let mut c = Case::new(173, "所有权纠纷".to_string()); c.close(); println!("{}", c.label());
self 的三种借法完全复用第 3 章法条:self 拿走所有权、&self 旁听、&mut self 发言。结构体更新语法 ..old 从旧档案拷字段,注意被移动的字段会使旧值部分失效——混合 Copy 与 Move 字段时尤其当心。
let a = Case::new(1, "甲案".into()); let b = a; // 整袋移动:String 字段带堆数据 // println!("{}", a.no); // E0382:整个 a 已失效
结构体没有实现 Copy(含 String 字段),赋值即整袋移交。想要 Debug 打印,派生即可:#[derive(Debug)] 后用 {:?} 打印。

Self 指代本类型;结构体更新语法 ..old 是拷贝剩余字段的速记,但"拷贝"对 Copy 字段是复制、对堆字段是移动,混装时就出案件。
struct Exhibit { id: u32, // Copy title: String, // 堆产权 sealed: bool, // Copy } fn main() { let old = Exhibit { id: 1, title: String::from("血迹样本"), sealed: true }; let new = Exhibit { id: 2, ..old // title 与 sealed 从 old 拿 }; // println!("{}", old.title); // E0382:title 已被移走 println!("{}", old.sealed); // bool 是 Copy,old.sealed 仍可用 println!("{} {}", new.id, new.title); }
移动发生在哪个字段完全取决于类型是否 Copy,编译器逐字段判。若确实要新旧并存,把 ..old 换成逐字段 title: old.title.clone(),成本写明,产权两清。
#[derive(Debug)] struct Dossier { pages: Vec<String> } impl Dossier { fn new() -> Self { // 关联函数:无 self,像构造器 Dossier { pages: Vec::new() } } fn add(&mut self, page: String) -> usize { self.pages.push(page); self.pages.len() } fn thickest<'a>(a: &'a Dossier, b: &'a Dossier) -> &'a Dossier { if a.pages.len() >= b.pages.len() { a } else { b } // 静态方法也可带生命周期 } } fn main() { let mut d1 = Dossier::new(); // :: 调用关联函数 let d2 = Dossier::new(); d1.add(String::from("封面")); let win = Dossier::thickest(&d1, &d2); println!("较厚 {:?}", win); }
区分记法:第一个参数带 self 的是方法,用点号调用;不带的是关联函数,用双冒号调用,构造器 new 是其最常见席位。同一类型可以开多个 impl 块,第 5 章 trait 实现各自成块时这个自由度会派上用场。
| 形态 | 声明 | 访问方式 | 适用 |
|---|---|---|---|
| 具名结构体 | struct E { id: u32 } |
点号 | 字段有独立语义 |
| 元组结构体 | struct Mm(u32); |
.0 |
给原始类型挂单位 |
| 单元结构体 | struct Marker; |
无字段 | 类型级标记、phantom 用法 |
元组结构体"新类型"模式是被低估的利器:struct CaseId(u32) 让编号类型与裸整数在类型层面分开,传错参数直接编译失败,第 7 章模块边界会再次引用这一手法。
struct CaseRoom { queue: Vec<String>, // 高频改动 config: Config, // 几乎只读 } struct Config { title: String } fn main() { let mut room = CaseRoom { queue: Vec::new(), config: Config { title: String::from("庭审") } }; let title = &room.config.title; // 不可变借用 config 部分 room.queue.push(String::from("证物")); // 可变借用 queue 部分:不同字段互不冲突 println!("{} 队列 {}", title, room.queue.len()); }
同一个结构体的不同字段可以同时一读一写,借用检查按字段切分。这与"整个 &mut self"形成对比:方法签名借整个 self 时,字段级的自由度被收走。大型结构体上优先暴露细粒度方法(push_to_queue 只借 queue),是既能过检又减少克隆的惯用法,第 5 章的 impl 块设计会直接用到。