练习题 🟡
你将学到: 在真实的硬件场景中动手练习正确构造模式——NVMe 管理命令、固件更新状态机、传感器流水线、PCIe phantom 类型、多协议健康检查和会话类型诊断协议。
交叉引用: ch02(练习 1)、ch05(练习 2)、ch06(练习 3)、ch09(练习 4)、ch10(练习 5)
练习题
练习 1:NVMe 管理命令(类型化命令)
为 NVMe 管理命令设计一个类型化命令接口:
Identify→IdentifyResponse(型号、序列号、固件版本)GetLogPage→SmartLog(温度、可用备用空间、读取的数据单元数)GetFeature→ 特定功能的响应
要求:
- 命令类型决定响应类型
- 没有运行时调度——仅静态调度
- 添加一个
NamespaceIdnewtype,防止将命名空间 ID 与其他u32混淆
提示: 遵循 ch02 的 IpmiCmd trait 模式,但使用 NVMe 特定的常量。
示例解决方案(练习 1)
use std::io;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NamespaceId(pub u32);
#[derive(Debug, Clone, PartialEq)]
pub struct IdentifyResponse {
pub model: String,
pub serial: String,
pub firmware_rev: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SmartLog {
pub temperature_kelvin: u16,
pub available_spare_pct: u8,
pub data_units_read: u64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ArbitrationFeature {
pub high_priority_weight: u8,
pub medium_priority_weight: u8,
pub low_priority_weight: u8,
}
/// 核心模式:关联类型为每个命令固定响应类型。
pub trait NvmeAdminCmd {
type Response;
fn opcode(&self) -> u8;
fn nsid(&self) -> Option<NamespaceId>;
fn parse_response(&self, raw: &[u8]) -> io::Result<Self::Response>;
}
pub struct Identify { pub nsid: NamespaceId }
impl NvmeAdminCmd for Identify {
type Response = IdentifyResponse;
fn opcode(&self) -> u8 { 0x06 }
fn nsid(&self) -> Option<NamespaceId> { Some(self.nsid) }
fn parse_response(&self, raw: &[u8]) -> io::Result<IdentifyResponse> {
if raw.len() < 12 {
return Err(io::Error::new(io::ErrorKind::InvalidData, "太短"));
}
Ok(IdentifyResponse {
model: String::from_utf8_lossy(&raw[0..4]).trim().to_string(),
serial: String::from_utf8_lossy(&raw[4..8]).trim().to_string(),
firmware_rev: String::from_utf8_lossy(&raw[8..12]).trim().to_string(),
})
}
}
pub struct GetLogPage { pub log_id: u8 }
impl NvmeAdminCmd for GetLogPage {
type Response = SmartLog;
fn opcode(&self) -> u8 { 0x02 }
fn nsid(&self) -> Option<NamespaceId> { None }
fn parse_response(&self, raw: &[u8]) -> io::Result<SmartLog> {
if raw.len() < 11 {
return Err(io::Error::new(io::ErrorKind::InvalidData, "太短"));
}
Ok(SmartLog {
temperature_kelvin: u16::from_le_bytes([raw[0], raw[1]]),
available_spare_pct: raw[2],
data_units_read: u64::from_le_bytes(raw[3..11].try_into().unwrap()),
})
}
}
pub struct GetFeature { pub feature_id: u8 }
impl NvmeAdminCmd for GetFeature {
type Response = ArbitrationFeature;
fn opcode(&self) -> u8 { 0x0A }
fn nsid(&self) -> Option<NamespaceId> { None }
fn parse_response(&self, raw: &[u8]) -> io::Result<ArbitrationFeature> {
if raw.len() < 3 {
return Err(io::Error::new(io::ErrorKind::InvalidData, "太短"));
}
Ok(ArbitrationFeature {
high_priority_weight: raw[0],
medium_priority_weight: raw[1],
low_priority_weight: raw[2],
})
}
}
/// 静态调度——编译器为每个命令类型单态化。
pub struct NvmeController;
impl NvmeController {
pub fn execute<C: NvmeAdminCmd>(&self, cmd: &C) -> io::Result<C::Response> {
// 从 cmd.opcode()/cmd.nsid() 构建 SQE,
// 提交到 SQ,等待 CQ,然后:
let raw = self.submit_and_read(cmd.opcode())?;
cmd.parse_response(&raw)
}
fn submit_and_read(&self, _opcode: u8) -> io::Result<Vec<u8>> {
// 真实实现与 /dev/nvme0 通信
Ok(vec![0; 512])
}
}
关键点:
NamespaceId(u32)防止将命名空间 ID 与任意u32值混淆。NvmeAdminCmd::Response是“类型索引“——execute()正好返回C::Response。- 完全静态调度:没有
Box<dyn …>,没有运行时向下转型。
练习 2:固件更新状态机(Type-State)
为 BMC 固件更新生命周期建模:
stateDiagram-v2
[*] --> Idle
Idle --> Uploading : begin_upload()
Uploading --> Uploading : send_chunk(data)
Uploading --> Verifying : finish_upload()
Uploading --> Idle : abort()
Verifying --> Applying : verify() ✅ + VerifiedImage token
Verifying --> Idle : verify() ❌ or abort()
Applying --> Rebooting : apply(token)
Rebooting --> Complete : reboot_complete()
Complete --> [*]
note right of Applying : 没有 abort() —— 不可逆
note right of Verifying : VerifiedImage 是证明令牌
要求:
- 每个状态是一个不同的类型
- 上传只能从 Idle 开始
- 验证需要上传完成
- 应用只能在成功验证后发生——接受一个
VerifiedImage证明令牌 - 重启是应用后的唯一选项
- 添加一个
abort()方法,在 Uploading 和 Verifying 状态可用(但在 Applying 不可用——太晚了)
提示: 结合 type-state(ch05)与 capability 令牌(ch04)。
示例解决方案(练习 2)
// --- 状态类型 ---
// 设计选择:这里我们将状态内联存储(`_state: S`),而不是使用
// `PhantomData<S>`(ch05 的方法)。这允许状态携带数据——
// 例如 `Uploading { bytes_sent: usize }` 跟踪进度。使用 `PhantomData`
// 当状态是纯标记(零大小)时;使用内联存储当
// 状态携带有意义的运行时数据时。
pub struct Idle;
pub struct Uploading { bytes_sent: usize } // 非 ZST——携带进度数据
pub struct Verifying;
pub struct Applying;
pub struct Rebooting;
pub struct Complete;
/// 证明令牌:只能在 verify() 内部构造。
pub struct VerifiedImage { _private: () }
pub struct FwUpdate<S> {
bmc_addr: String,
_state: S,
}
impl FwUpdate<Idle> {
pub fn new(bmc_addr: &str) -> Self {
FwUpdate { bmc_addr: bmc_addr.to_string(), _state: Idle }
}
pub fn begin_upload(self) -> FwUpdate<Uploading> {
FwUpdate { bmc_addr: self.bmc_addr, _state: Uploading { bytes_sent: 0 } }
}
}
impl FwUpdate<Uploading> {
pub fn send_chunk(mut self, chunk: &[u8]) -> Self {
self._state.bytes_sent += chunk.len();
self
}
pub fn finish_upload(self) -> FwUpdate<Verifying> {
FwUpdate { bmc_addr: self.bmc_addr, _state: Verifying }
}
/// Abort 在上传期间可用——返回 Idle。
pub fn abort(self) -> FwUpdate<Idle> {
FwUpdate { bmc_addr: self.bmc_addr, _state: Idle }
}
}
impl FwUpdate<Verifying> {
/// 成功时,返回下一个状态和 VerifiedImage 证明令牌。
pub fn verify(self) -> Result<(FwUpdate<Applying>, VerifiedImage), FwUpdate<Idle>> {
// 真实实现:检查 CRC、签名、兼容性
let token = VerifiedImage { _private: () };
Ok((
FwUpdate { bmc_addr: self.bmc_addr, _state: Applying },
token,
))
}
/// Abort 在验证期间可用。
pub fn abort(self) -> FwUpdate<Idle> {
FwUpdate { bmc_addr: self.bmc_addr, _state: Idle }
}
}
impl FwUpdate<Applying> {
/// 消费 VerifiedImage 证明——不能在没有验证的情况下应用。
/// 注意:这里没有 abort() 方法——一旦开始刷写,就太危险了。
pub fn apply(self, _proof: VerifiedImage) -> FwUpdate<Rebooting> {
FwUpdate { bmc_addr: self.bmc_addr, _state: Rebooting }
}
}
impl FwUpdate<Rebooting> {
pub fn wait_for_reboot(self) -> FwUpdate<Complete> {
FwUpdate { bmc_addr: self.bmc_addr, _state: Complete }
}
}
impl FwUpdate<Complete> {
pub fn version(&self) -> &str { "2.1.0" }
}
// 用法:
// let fw = FwUpdate::new("192.168.1.100")
// .begin_upload()
// .send_chunk(b"image_data")
// .finish_upload();
// let (fw, proof) = fw.verify().map_err(|_| "verify failed")?;
// let fw = fw.apply(proof).wait_for_reboot();
// println!("新版本:{}", fw.version());
关键点:
abort()仅存在于FwUpdate<Uploading>和FwUpdate<Verifying>上——在FwUpdate<Applying>上调用它是编译错误,而不是运行时检查。VerifiedImage有私有字段,所以只有verify()能创建它。apply()消费证明令牌——你不能跳过验证。
练习 3:传感器读数流水线(量纲分析)
构建一个完整的传感器流水线:
- 定义 newtype:
RawAdc、Celsius、Fahrenheit、Volts、Millivolts、Watts - 实现
From<Celsius> for Fahrenheit和反向转换 - 创建
impl Mul<Volts, Output=Watts> for Amperes(P = V × I) - 构建一个
Threshold<T>泛型检查器 - 编写流水线:ADC → 校准 → 阈值检查 → 结果
编译器应该拒绝:将 Celsius 与 Volts 比较、将 Watts 与 Rpm 相加、在期望 Volts 的地方传递 Millivolts。
示例解决方案(练习 3)
use std::ops::{Add, Sub, Mul};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct RawAdc(pub u16);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Celsius(pub f64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Fahrenheit(pub f64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Volts(pub f64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Millivolts(pub f64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Amperes(pub f64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Watts(pub f64);
// --- 安全转换 ---
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self { Fahrenheit(c.0 * 9.0 / 5.0 + 32.0) }
}
impl From<Fahrenheit> for Celsius {
fn from(f: Fahrenheit) -> Self { Celsius((f.0 - 32.0) * 5.0 / 9.0) }
}
impl From<Millivolts> for Volts {
fn from(mv: Millivolts) -> Self { Volts(mv.0 / 1000.0) }
}
impl From<Volts> for Millivolts {
fn from(v: Volts) -> Self { Millivolts(v.0 * 1000.0) }
}
// --- 相同单位类型的算术 ---
// 注意:添加绝对温度(25°C + 30°C)在物理上
// 是有问题的——参见 ch06 关于 ΔT newtype 的讨论以获取更严格的方法。
// 这里为了练习保持简单。
impl Add for Celsius {
type Output = Celsius;
fn add(self, rhs: Self) -> Celsius { Celsius(self.0 + rhs.0) }
}
impl Sub for Celsius {
type Output = Celsius;
fn sub(self, rhs: Self) -> Celsius { Celsius(self.0 - rhs.0) }
}
// P = V × I (跨单位乘法)
impl Mul<Amperes> for Volts {
type Output = Watts;
fn mul(self, rhs: Amperes) -> Watts { Watts(self.0 * rhs.0) }
}
// --- 泛型阈值检查器 ---
// 练习 3 扩展了 ch06 的 Threshold,带有泛型 ThresholdResult<T>
// 携带触发的读数——这是 ch06 更简单的
// ThresholdResult { Normal, Warning, Critical } 枚举的演进。
pub enum ThresholdResult<T> {
Normal(T),
Warning(T),
Critical(T),
}
pub struct Threshold<T> {
pub warning: T,
pub critical: T,
}
// 泛型 impl——适用于任何支持 PartialOrd 的单位类型。
impl<T: PartialOrd + Copy> Threshold<T> {
pub fn check(&self, reading: T) -> ThresholdResult<T> {
if reading >= self.critical {
ThresholdResult::Critical(reading)
} else if reading >= self.warning {
ThresholdResult::Warning(reading)
} else {
ThresholdResult::Normal(reading)
}
}
}
// 现在 `Threshold<Rpm>`、`Threshold<Volts>` 等都自动工作。
// --- 流水线:ADC → 校准 → 阈值 → 结果 ---
pub struct CalibrationParams {
pub scale: f64, // ADC 计数/°C
pub offset: f64, // ADC 0 时的°C
}
pub fn calibrate(raw: RawAdc, params: &CalibrationParams) -> Celsius {
Celsius(raw.0 as f64 / params.scale + params.offset)
}
pub fn sensor_pipeline(
raw: RawAdc,
params: &CalibrationParams,
threshold: &Threshold<Celsius>,
) -> ThresholdResult<Celsius> {
let temp = calibrate(raw, params);
threshold.check(temp)
}
// 编译时安全——这些将**无法编译**:
// let _ = Celsius(25.0) + Volts(12.0); // 错误:类型不匹配
// let _: Millivolts = Volts(1.0); // 错误:没有隐式强制转换
// let _ = Watts(100.0) + Rpm(3000); // 错误:类型不匹配
关键点:
- 每个物理单位是一个不同的类型——不会意外混淆。
Mul<Amperes> for Volts生成Watts,将 P = V × I 编码到类型系统中。- 对相关单位(mV ↔ V、°C ↔ °F)的显式
From转换。 Threshold<Celsius>只接受Celsius——不能意外地阈值检查 RPM。
练习 4:PCIe Capability 遍历(Phantom 类型 + 验证边界)
为 PCIe capability 链表建模:
RawCapability—— 来自配置空间的未验证字节ValidCapability—— 已解析和验证(通过 TryFrom)- 每种 capability 类型(MSI、MSI-X、PCIe Express、电源管理)有自己的 phantom 类型寄存器布局
- 遍历列表返回
ValidCapability值的迭代器
提示: 结合验证边界(ch07)与 phantom 类型(ch09)。
示例解决方案(练习 4)
use std::marker::PhantomData;
// --- Phantom 标记用于 capability 类型 ---
pub struct Msi;
pub struct MsiX;
pub struct PciExpress;
pub struct PowerMgmt;
// PCI capability ID 来自规范
const CAP_ID_PM: u8 = 0x01;
const CAP_ID_MSI: u8 = 0x05;
const CAP_ID_PCIE: u8 = 0x10;
const CAP_ID_MSIX: u8 = 0x11;
/// 未验证的字节——可能是垃圾数据。
#[derive(Debug)]
pub struct RawCapability {
pub id: u8,
pub next_ptr: u8,
pub data: Vec<u8>,
}
/// 已验证和类型标记的 capability。
#[derive(Debug)]
pub struct ValidCapability<Kind> {
id: u8,
next_ptr: u8,
data: Vec<u8>,
_kind: PhantomData<Kind>,
}
// --- TryFrom:解析而非验证边界 ---
impl TryFrom<RawCapability> for ValidCapability<PowerMgmt> {
type Error = &'static str;
fn try_from(raw: RawCapability) -> Result<Self, Self::Error> {
if raw.id != CAP_ID_PM { return Err("不是 PM capability"); }
if raw.data.len() < 2 { return Err("PM 数据太短"); }
Ok(ValidCapability {
id: raw.id, next_ptr: raw.next_ptr,
data: raw.data, _kind: PhantomData,
})
}
}
impl TryFrom<RawCapability> for ValidCapability<Msi> {
type Error = &'static str;
fn try_from(raw: RawCapability) -> Result<Self, Self::Error> {
if raw.id != CAP_ID_MSI { return Err("不是 MSI capability"); }
if raw.data.len() < 6 { return Err("MSI 数据太短"); }
Ok(ValidCapability {
id: raw.id, next_ptr: raw.next_ptr,
data: raw.data, _kind: PhantomData,
})
}
}
// (类似的 TryFrom 实现用于 MsiX、PciExpress——为简洁省略)
// --- 类型安全访问器:仅可用于正确的 capability ---
impl ValidCapability<PowerMgmt> {
pub fn pm_control(&self) -> u16 {
u16::from_le_bytes([self.data[0], self.data[1]])
}
}
impl ValidCapability<Msi> {
pub fn message_control(&self) -> u16 {
u16::from_le_bytes([self.data[0], self.data[1]])
}
pub fn vectors_requested(&self) -> u32 {
1 << ((self.message_control() >> 1) & 0x07)
}
}
impl ValidCapability<MsiX> {
pub fn table_size(&self) -> u16 {
(u16::from_le_bytes([self.data[0], self.data[1]]) & 0x07FF) + 1
}
}
// --- Capability 遍历器:遍历链表 ---
pub struct CapabilityWalker<'a> {
config_space: &'a [u8],
next_ptr: u8,
}
impl<'a> CapabilityWalker<'a> {
pub fn new(config_space: &'a [u8]) -> Self {
// Capability 指针位于 PCI 配置空间偏移 0x34
let first_ptr = if config_space.len() > 0x34 {
config_space[0x34]
} else { 0 };
CapabilityWalker { config_space, next_ptr: first_ptr }
}
}
impl<'a> Iterator for CapabilityWalker<'a> {
type Item = RawCapability;
fn next(&mut self) -> Option<RawCapability> {
if self.next_ptr == 0 { return None; }
let off = self.next_ptr as usize;
if off + 2 > self.config_space.len() { return None; }
let id = self.config_space[off];
let next = self.config_space[off + 1];
let end = if next > 0 { next as usize } else {
(off + 16).min(self.config_space.len())
};
let data = self.config_space[off + 2..end].to_vec();
self.next_ptr = next;
Some(RawCapability { id, next_ptr: next, data })
}
}
// 用法:
// for raw_cap in CapabilityWalker::new(&config_space) {
// if let Ok(pm) = ValidCapability::<PowerMgmt>::try_from(raw_cap) {
// println!("PM control: 0x{:04X}", pm.pm_control());
// }
// }
关键点:
RawCapability→ValidCapability<Kind>是解析而非验证边界。pm_control()仅存在于ValidCapability<PowerMgmt>上——在 MSI capability 上调用它是编译错误。CapabilityWalker迭代器生成原始 capability;调用者用TryFrom验证他们关心的那些。
练习 5:多协议健康检查(Capability 混合)
创建一个健康检查框架:
- 定义原料 trait:
HasIpmi、HasRedfish、HasNvmeCli、HasGpio - 创建混合 trait:
ThermalHealthMixin(需要 HasIpmi + HasGpio)—— 读取温度,检查警报StorageHealthMixin(需要 HasNvmeCli)—— SMART 数据检查BmcHealthMixin(需要 HasIpmi + HasRedfish)—— 交叉验证 BMC 数据
- 构建一个
FullPlatformController实现所有原料 trait - 构建一个
StorageOnlyController只实现HasNvmeCli - 验证
StorageOnlyController获得StorageHealthMixin但不获得其他混合
示例解决方案(练习 5)
// --- 原料 trait ---
pub trait HasIpmi {
fn ipmi_read_sensor(&self, id: u8) -> f64;
}
pub trait HasRedfish {
fn redfish_get(&self, path: &str) -> String;
}
pub trait HasNvmeCli {
fn nvme_smart_log(&self, dev: &str) -> SmartData;
}
pub trait HasGpio {
fn gpio_read_alert(&self, pin: u8) -> bool;
}
pub struct SmartData {
pub temperature_kelvin: u16,
pub spare_pct: u8,
}
// --- 混合 trait 带有 blanket impl ---
pub trait ThermalHealthMixin: HasIpmi + HasGpio {
fn thermal_check(&self) -> ThermalStatus {
let temp = self.ipmi_read_sensor(0x01);
let alert = self.gpio_read_alert(12);
ThermalStatus { temperature: temp, alert_active: alert }
}
}
impl<T: HasIpmi + HasGpio> ThermalHealthMixin for T {}
pub trait StorageHealthMixin: HasNvmeCli {
fn storage_check(&self) -> StorageStatus {
let smart = self.nvme_smart_log("/dev/nvme0");
StorageStatus {
temperature_ok: smart.temperature_kelvin < 343, // 70 °C
spare_ok: smart.spare_pct > 10,
}
}
}
impl<T: HasNvmeCli> StorageHealthMixin for T {}
pub trait BmcHealthMixin: HasIpmi + HasRedfish {
fn bmc_health(&self) -> BmcStatus {
let ipmi_temp = self.ipmi_read_sensor(0x01);
let rf_temp = self.redfish_get("/Thermal/Temperatures/0");
BmcStatus { ipmi_temp, redfish_temp: rf_temp, consistent: true }
}
}
impl<T: HasIpmi + HasRedfish> BmcHealthMixin for T {}
pub struct ThermalStatus { pub temperature: f64, pub alert_active: bool }
pub struct StorageStatus { pub temperature_ok: bool, pub spare_ok: bool }
pub struct BmcStatus { pub ipmi_temp: f64, pub redfish_temp: String, pub consistent: bool }
// --- 完整平台:所有原料 → 所有三个混合免费获得 ---
pub struct FullPlatformController;
impl HasIpmi for FullPlatformController {
fn ipmi_read_sensor(&self, _id: u8) -> f64 { 42.0 }
}
impl HasRedfish for FullPlatformController {
fn redfish_get(&self, _path: &str) -> String { "42.0".into() }
}
impl HasNvmeCli for FullPlatformController {
fn nvme_smart_log(&self, _dev: &str) -> SmartData {
SmartData { temperature_kelvin: 310, spare_pct: 95 }
}
}
impl HasGpio for FullPlatformController {
fn gpio_read_alert(&self, _pin: u8) -> bool { false }
}
// --- 仅存储:只有 HasNvmeCli → 只有 StorageHealthMixin ---
pub struct StorageOnlyController;
impl HasNvmeCli for StorageOnlyController {
fn nvme_smart_log(&self, _dev: &str) -> SmartData {
SmartData { temperature_kelvin: 315, spare_pct: 80 }
}
}
// StorageOnlyController 自动获得 storage_check()。
// 在它上面调用 thermal_check() 或 bmc_health() 是**编译错误**。
关键点:
- Blanket
impl<T: HasIpmi + HasGpio> ThermalHealthMixin for T {}—— 任何实现两个原料的类型自动获得混合。 StorageOnlyController只实现HasNvmeCli,所以编译器授予它StorageHealthMixin但拒绝thermal_check()和bmc_health()—— 不需要运行时检查。- 添加新混合(例如
NetworkHealthMixin: HasRedfish + HasGpio)是一个 trait + 一个 blanket impl——现有控制器如果符合条件就自动获得它。
练习 6:会话类型诊断协议(一次性 + Type-State)
设计一个带有一次性测试执行令牌的诊断会话:
DiagSession从Setup状态开始- 转换到
Running状态——发放N个执行令牌(每个测试用例一个) - 每个
TestToken在测试运行时被消费——防止运行相同的测试两次 - 在所有令牌被消费后,转换到
Complete状态 - 生成报告(仅在
Complete状态)
高级: 使用 const 泛型 N 在类型级别跟踪剩余多少测试。
示例解决方案(练习 6)
// --- 状态类型 ---
pub struct Setup;
pub struct Running;
pub struct Complete;
/// 一次性测试令牌。不可 Clone,不可 Copy——在使用时被消费。
pub struct TestToken {
test_name: String,
}
#[derive(Debug)]
pub struct TestResult {
pub test_name: String,
pub passed: bool,
}
pub struct DiagSession<S> {
name: String,
results: Vec<TestResult>,
_state: S,
}
impl DiagSession<Setup> {
pub fn new(name: &str) -> Self {
DiagSession {
name: name.to_string(),
results: Vec::new(),
_state: Setup,
}
}
/// 转换到 Running——为每个测试用例发放一个令牌。
pub fn start(self, test_names: &[&str]) -> (DiagSession<Running>, Vec<TestToken>) {
let tokens = test_names.iter()
.map(|n| TestToken { test_name: n.to_string() })
.collect();
(
DiagSession {
name: self.name,
results: Vec::new(),
_state: Running,
},
tokens,
)
}
}
impl DiagSession<Running> {
/// 消费一个令牌来运行一个测试。move 防止重复运行。
pub fn run_test(mut self, token: TestToken) -> Self {
let passed = true; // 真实代码在这里运行实际诊断
self.results.push(TestResult {
test_name: token.test_name,
passed,
});
self
}
/// 转换到 Complete。
///
/// **注意:** 此解决方案**不**强制所有令牌已被消费——`finish()` 可以在
/// 仍有令牌剩余时调用。令牌将被简单地丢弃(它们不是 `#[must_use]`)。
/// 对于完整的编译时强制,使用下面的"高级"变体,
/// 其中 `finish()` 仅在 `DiagSession<Running, 0>` 上可用。
pub fn finish(self) -> DiagSession<Complete> {
DiagSession {
name: self.name,
results: self.results,
_state: Complete,
}
}
}
impl DiagSession<Complete> {
/// 报告**仅**在 Complete 状态可用。
pub fn report(&self) -> String {
let total = self.results.len();
let passed = self.results.iter().filter(|r| r.passed).count();
format!("{}: {}/{} 通过", self.name, passed, total)
}
}
// 用法:
// let session = DiagSession::new("GPU 压力测试");
// let (mut session, tokens) = session.start(&["vram", "compute", "thermal"]);
// for token in tokens {
// session = session.run_test(token);
// }
// let session = session.finish();
// println!("{}", session.report()); // "GPU 压力测试:3/3 通过"
//
// // 这些将**无法编译**:
// // session.run_test(used_token); → 错误:使用已移动的值
// // running_session.report(); → 错误:没有方法 `report` 在 DiagSession<Running> 上
关键点:
TestToken不是Clone或Copy—— 通过run_test(token)消费它时 move 它,所以重新运行相同的测试是编译错误。report()仅存在于DiagSession<Complete>上——在运行中途调用它是不可能的。- 高级变体将使用
DiagSession<Running, N>与 const 泛型,其中run_test返回DiagSession<Running, {N-1}>且finish仅在DiagSession<Running, 0>上可用——这确保在结束前所有令牌都被消费。
关键要点
- 用真实协议练习 —— NVMe、固件更新、传感器流水线、PCIe 都是这些模式的真实世界目标。
- 每个练习映射到核心章节 —— 使用交叉引用来在尝试之前复习模式。
- 解决方案使用可展开的详情 —— 在揭示解决方案之前尝试每个练习。
- 在练习 5 中组合模式 —— 多协议健康检查结合了类型化命令、量纲类型和验证边界。
- 会话类型(练习 6)是前沿 —— 它们强制跨通道的消息排序,将 type-state 扩展到分布式系统。