06|结构体与方法:给统计结果一个类型
目标:用结构体保存相关统计值,并通过借用自身的方法输出摘要。
当统计结果只有一个整数,函数直接返回 usize 很自然。但同时返回字节数、字符数、词数和行数时,四个数字很容易弄错顺序。结构体让字段有名字,把结果的含义保留在代码中。
struct 定义数据形状;创建实例时必须提供各字段。impl 为类型定义关联函数和方法。接收 &self 的方法只借用当前实例,调用之后还能继续使用结果。若方法需要修改字段,用 &mut self;若要消耗实例,用 self。这些规则与前面的普通函数参数一致。
本课先保留两个字段,下一步接入项目时再使用已学过的四项统计。不要为了展示面向对象语法引入继承:Rust 的结构体不采用类继承模型,后面会用 trait 表达共享行为。
从输入构造一个报告
创建 examples/report.rs:
struct Report {
bytes: usize,
chars: usize,
}
impl Report {
fn from_text(text: &str) -> Self {
Self {
bytes: text.len(),
chars: text.chars().count(),
}
}
fn summary(&self) -> String {
format!("bytes={} chars={}", self.bytes, self.chars)
}
}
fn main() {
let report = Report::from_text("你好");
println!("{}", report.summary());
println!("bytes again={}", report.bytes);
}
Self 在这里就是 Report。from_text 没有 self 参数,是通过类型名调用的关联函数;summary 是通过实例调用的方法。format! 和 println! 使用相似的格式语法,但返回 String,不直接打印。
运行前先预测:调用 summary() 后,再访问 report.bytes 是否仍有效?
cargo run --quiet --example report
预期输出:
bytes=6 chars=2
bytes again=6
构造函数借用输入,计算后只留下整数,所以报告不依赖原文本继续存活。summary 也仅借用报告,生成的摘要字符串有自己的所有权。这种“借用输入,返回拥有的结果”是小型数据处理程序中很好用的接口。
改一个字段,追踪所有受影响位置
- 给结构体增加
words: usize。 - 在构造处增加
words: text.split_whitespace().count()。 - 给摘要格式增加
words={},并传入self.words。 - 再运行,第一行应为
bytes=6 chars=2 words=1。
独立练习:恢复主示例后增加方法 is_ascii_width(&self) -> bool,返回字节数是否等于标量值数。对 "Rust" 与 "你好" 分别断言为真、假。这里没有在解析编码,只利用有效 UTF-8 中 ASCII 标量值占一个字节的性质。
参考修改与可执行检查
在已有 impl Report 内加入以下局部方法;它依赖上面完整的类型定义:
fn is_ascii_width(&self) -> bool {
self.bytes == self.chars
}
把 main 完整替换为以下函数,保留 Report 和它原来的方法:
fn main() {
let ascii = Report::from_text("Rust");
let chinese = Report::from_text("你好");
assert_eq!(ascii.is_ascii_width(), true);
assert_eq!(chinese.is_ascii_width(), false);
println!("{} / {}", ascii.summary(), chinese.summary());
}
运行同一命令,预期 bytes=4 chars=4 / bytes=6 chars=2。本课没有把字段设为公共接口,跨文件的可见性在第 12 课处理。
如果构造时报缺少字段,检查每个构造表达式,不只是结构体声明。如果报找不到方法,检查方法是否放在 impl Report 内,以及是否使用了正确的接收者。调用 Report::summary() 时缺少实例,就是把方法误当成无接收者的关联函数了。
本课新增 examples/report.rs,为最终项目确定“统计函数返回 Report,展示代码读取 Report”的分工。