建模电梯事件系统——Elevator Events 练习完整解析)
Comprehensive Rust 实战用带数据枚举Enums with Data建模电梯事件系统——Elevator Events 练习完整解析【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust本篇技术指南围绕 Google Android 团队维护的 Rust 课程comprehensive-rust中 user-defined-types/solution.md 这一练习解决方案展开系统讲解如何用 Rust 的枚举enum、类型别名type alias与#[derive(Debug)]为电梯控制系统建模事件类型。读完本文你将掌握「带数据的枚举变体」「嵌套枚举」「单元变体与元组变体」的组合用法并能独立完成一个类型安全的领域事件建模方案。练习背景什么是 Elevator Events本练习来自课程 User-Defined Types 模块位于 exercise.md。任务是为电梯控制系统设计一个表示「事件」的数据结构并编写若干构造事件的函数使main函数在无编译错误的情况下运行。练习要求如下自行定义类型与构造函数用于构造各类电梯事件使用#[derive(Debug)]使类型可以通过{:?}格式化打印本练习只需「创建并填充数据结构」从结构体中取出数据是后续课程的内容。完整的练习框架代码与main函数位于 exercise.rs 中以ANCHOR注释标记了各代码段。本练习模拟的电梯场景包含六类事件楼层呼叫按钮被按下、轿厢到达某楼层、轿厢门打开、轿厢门关闭等。完整解决方案代码exercise.rs 中的solution锚点第 16 行起给出了官方标准答案。以下是完整可运行版本#![allow(dead_code)] #[derive(Debug)] /// An event in the elevator system that the controller must react to. enum Event { /// A button was pressed. ButtonPressed(Button), /// The car has arrived at the given floor. CarArrived(Floor), /// The cars doors have opened. CarDoorOpened, /// The cars doors have closed. CarDoorClosed, } /// A floor is represented as an integer. type Floor i32; /// A direction of travel. #[derive(Debug)] enum Direction { Up, Down, } /// A user-accessible button. #[derive(Debug)] enum Button { /// A button in the elevator lobby on the given floor. LobbyCall(Direction, Floor), /// A floor button within the car. CarFloor(Floor), } /// The car has arrived on the given floor. fn car_arrived(floor: i32) - Event { Event::CarArrived(floor) } /// The car doors have opened. fn car_door_opened() - Event { Event::CarDoorOpened } /// The car doors have closed. fn car_door_closed() - Event { Event::CarDoorClosed } /// A directional button was pressed in an elevator lobby on the given floor. fn lobby_call_button_pressed(floor: i32, dir: Direction) - Event { Event::ButtonPressed(Button::LobbyCall(dir, floor)) } /// A floor button was pressed in the elevator car. fn car_floor_button_pressed(floor: i32) - Event { Event::ButtonPressed(Button::CarFloor(floor)) } fn main() { println!( A ground floor passenger has pressed the up button: {:?}, lobby_call_button_pressed(0, Direction::Up) ); println!(The car has arrived on the ground floor: {:?}, car_arrived(0)); println!(The car door opened: {:?}, car_door_opened()); println!( A passenger has pressed the 3rd floor button: {:?}, car_floor_button_pressed(3) ); println!(The car door closed: {:?}, car_door_closed()); println!(The car has arrived on the 3rd floor: {:?}, car_arrived(3)); }运行后输出{:?}逐层打印每个枚举值A ground floor passenger has pressed the up button: ButtonPressed(LobbyCall(Up, 0)) The car has arrived on the ground floor: CarArrived(0) The car door opened: CarDoorOpened A passenger has pressed the 3rd floor button: ButtonPressed(CarFloor(3)) The car door closed: CarDoorClosed The car has arrived on the 3rd floor: CarArrived(3)核心知识点一带数据的枚举变体Enums with Data这是本解决方案最核心的设计。与仅列举「无数据」变体的简单枚举不同Rust 允许每个变体携带不同类型的数据Event::CarArrived(Floor)携带一个整数楼层号属于元组变体tuple variantEvent::ButtonPressed(Button)携带一个嵌套的Button枚举值Event::CarDoorOpened与Event::CarDoorClosed不携带任何数据属于单元变体unit variant。正是这种「变体可携带数据」的能力让Event能够以类型安全的方式表达丰富的系统状态编译器会保证CarArrived永远带一个楼层号、ButtonPressed永远带一个按钮对象不存在「忘带参数」或「传错参数类型」的可能。判别值discriminant与存储课程 enums.md 指出Rust 在枚举值中额外存储一个判别值discriminant以便在运行时区分当前是哪一个变体。同时 Rust 会用最小所需空间存储判别值若必要使用最小尺寸的整型若变体允许的取值没有覆盖全部位模式则利用非法位模式编码判别值即「空位优化niche optimization」。例如Optionu8用指针本身或NULL表示None变体size_of::T()与size_of::OptionT()相等。如需与 C 兼容可显式指定判别值类型例如#[repr(u32)] enum Bar { A, B 10000, C }其中A为 0、B为 10000、C自动接续为 10001若不加repr编译器会按最大取值选择合适大小的整型如 10001 需要 2 字节。核心知识点二类型别名Type Aliases解决方案中第 38 行/// A floor is represented as an integer. type Floor i32;type Floor i32给i32赋予了一个语义化名称显著提升了可读性——看到CarArrived(Floor)比CarArrived(i32)更容易理解业务含义。课程 aliases.md 进一步说明类型别名创建的是「另一个名字」两种类型可以互换使用Floor在编译器眼里仍然只是i32不会产生新的独立类型别名在处理冗长复杂类型时更有价值例如use std::cell::RefCell; use std::sync::{Arc, RwLock}; type PlayerInventory RwLockVecArcRefCellItem;C 程序员可以将其类比为typedef若要真正创建「独立的、不能与i32混用」的类型应使用newtype单字段元组结构体如struct FloorCount(usize)。相关深入内容见课程 tuple-structs.md 与 Idiomatic Rust 模块的 newtype-pattern 章节newtype-pattern.md。核心知识点三#[derive(Debug)]自动派生解决方案中Event、Direction、Button三个枚举都标注了#[derive(Debug)]#[derive(Debug)] enum Event { ... }该属性让编译器自动生成将类型格式化为{:?}输出的代码。没有它直接用println!({:?}, event)会报错因为Debugtrait 未实现。课程 named-structs.md 与 enums.md 均把#[derive(Debug)]作为让枚举/结构体可打印的标准手法。若要自定义打印格式才需要手动实现std::fmt::Debugtrait。核心知识点四嵌套枚举Nested Enums的层次化建模Button枚举被嵌套在Event::ButtonPressed变体内部enum Button { /// A button in the elevator lobby on the given floor. LobbyCall(Direction, Floor), /// A floor button within the car. CarFloor(Floor), }这种层次化结构是 Rust 建模复杂领域时的常见手法Event是顶层事件类型ButtonPressed携带一个细化的Button类型而Button又可以携带Direction上/下与Floor楼层号。数据逐层收窄表达力强且类型安全。为什么把Button独立成枚举课程讲解要点见 solution.md 的details部分建议与读者讨论为什么不直接在Event上定义LobbyCallButtonPressed和CarFloorButtonPressed两个变体两种方案都合法但将「按钮」这一相关概念聚合为独立的Button枚举可以让代码更清晰所有按钮相关的子类型收敛到一处便于统一演进Event::ButtonPressed(Button)的单一变体比两个并列变体更简洁后续若新增按钮类型如紧急呼叫按钮只需扩展Button枚举Event无需改动。同时注意Button::LobbyCall(Direction, Floor)还展示了元组变体携带多个数据项的能力。核心知识点五单元变体 vs 元组变体在Event中Event::CarDoorOpened、Event::CarDoorClosed是单元变体unit variant——不携带任何数据仅表示「一个状态」Event::CarArrived(Floor)、Event::ButtonPressed(Button)是元组变体tuple variant——携带相关数据。这与课程 named-structs.md 中提到的零字段结构体struct Foo;是同一思想的枚举侧体现当「事件本身即信息」时无需数据当「事件需要附加上下文」时携带数据。构造函数用函数封装枚举创建逻辑解决方案中的六个构造函数将「业务动作」映射为「事件值」把创建逻辑封装为语义化函数调用方无需了解枚举内部结构函数返回的事件car_arrived(floor: i32)Event::CarArrived(floor)car_door_opened()Event::CarDoorOpenedcar_door_closed()Event::CarDoorClosedlobby_call_button_pressed(floor: i32, dir: Direction)Event::ButtonPressed(Button::LobbyCall(dir, floor))car_floor_button_pressed(floor: i32)Event::ButtonPressed(Button::CarFloor(floor))注意lobby_call_button_pressed中元组变体的字段顺序Button::LobbyCall(dir, floor)参数顺序需与enum Button中声明顺序一致。main函数随后依次打印从「一楼乘客按下上行按钮」到「轿厢到达三楼」的完整事件序列直观展示了事件驱动系统的输入流。练习中的几个细节与常见疑问为什么练习代码顶部有#![allow(dead_code)]练习 exercise.md 的details部分解释本练习中Event类型仅被打印输出。由于编译器对死代码检查的处理方式它会认为这些类型「未被使用」。为专注于练习本身用#![allow(dead_code)]抑制该警告即可练习中可忽略它。解答题中如何构造事件练习框架中main是给定的来自 exercise.rs 的main锚点你只需补齐Event的变体定义与各todo!()函数体让main编译通过并输出正确结果。#[derive(Debug)]是必须的否则{:?}无法打印。更深入用户自定义类型的全景本解决方案覆盖了枚举、类型别名两个主题。要全面掌握 User-Defined Types 模块可继续阅读同一模块下的其余章节named-structs.md具名字段结构体与字段初始化简写、tuple-structs.mdnewtype 模式、const.md编译期常量与const fn、static.md具有对象标识的全局变量。其中const在编译期求值并在使用处内联只有const fn才能参与编译期常量生成static在整个程序执行期间存活、不会被移动拥有真实的内存地址与对象标识可用于MutexT等内部可变类型因可从任意线程访问static必须是Sync的常用OnceLock支持首次使用初始化。小结通过 Elevator Events 练习你将 Rust 用户自定义类型的核心能力串联了起来带数据的枚举用类型系统承载领域状态杜绝「非法状态」嵌套枚举为复杂业务领域提供层次化建模类型别名为原始类型赋予语义名称#[derive(Debug)]一行完成可打印格式化构造函数封装事件创建逻辑让业务代码与数据结构解耦。这套组合拳是 Rust 领域建模尤其是事件驱动系统、状态机、协议解析等场景的基础功。完整的官方解答可随时查阅 exercise.rs对比自己的实现后续课程还将覆盖从这些结构中取出数据模式匹配的内容见 pattern-matching.md 模块。【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考