尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Rust 入门实战(5):结构体枚举与 trait

Rust 入门实战(5):结构体枚举与 trait 上一篇让失败通过Result显式流动本篇继续强化类型建模结构体把相关数据与不变量收拢枚举让状态集合封闭trait 则用行为而非继承连接不同类型。任务程序将从散落字段升级为可维护领域模型。一、痛点布尔字段很快制造非法组合若任务同时有done、cancelled、archived三个布尔值理论上产生八种组合但业务可能只允许待办、完成、取消三种。多个布尔无法阻止“既完成又取消”。枚举把有效状态列成有限变体match在新增状态时提示所有需修改的位置这就是“让非法状态不可表示”。结构体分具名字段、元组结构体和单元结构体。领域对象通常用具名字段TaskId(u64)这类新类型用元组结构体防止把用户编号误传成任务编号且没有运行时包装成本。实现块impl放构造函数和方法构造函数虽没有关键字约束但惯例命名new并返回Self或ResultSelf,E。字段是否公开决定不变量能否被绕过。若外部可直接写任意标题构造器的非空验证就失去意义。库边界应保持字段私有提供最小查询和变更方法应用内部可按复杂度适度放宽避免机械封装。二、原理枚举变体可以携带不同数据Rust 枚举不是整数常量的别名。每个变体可携带结构化数据因此状态与该状态专属信息能绑定完成状态带完成时间取消状态带原因。Option与Result本身就是这种代数数据类型。#[derive(Debug, Clone, PartialEq)]enumStatus{Pending,Done{completed_by:String},Cancelled{reason:String},}#[derive(Debug)]structTask{id:u64,title:String,status:Status,}implTask{fnnew(id:u64,title:implIntoString)-ResultSelf,String{lettitletitle.into();iftitle.trim().is_empty(){returnErr(标题不能为空.into());}Ok(Self{id,title,status:Status::Pending})}fncomplete(mutself,user:implIntoString)-Result(),String{matchself.status{Status::Pending{self.statusStatus::Done{completed_by:user.into()};Ok(())}_Err(只有待办任务可以完成.into()),}}}fnmain(){letmuttaskTask::new(7,设计领域模型).unwrap();task.complete(小林).unwrap();println!(id{} title{},task.id,task.title);println!(status{:?},task.status);letcancelledStatus::Cancelled{reason:重复任务.into()};println!(sample{cancelled:?});}运行输出id7 title设计领域模型 statusDone { completed_by: 小林 } sampleCancelled { reason: 重复任务 }构造器使用impl IntoString调用者可传str或String对象最终仍拥有数据。变更方法返回Result并集中状态转换外部无法绕过规则。derive自动实现调试、克隆和比较能力只应派生确有语义的 trait例如含密钥类型不宜随意Debug。三、实现用 trait 抽象能力trait 声明一组方法契约类型通过impl Trait for Type实现。泛型参数T: Trait使用静态分派编译器为具体类型生成代码通常易优化dyn Trait使用动态分派可在同一集合放不同实现但需要指针间接访问。选择依据是扩展边界不是孰优孰劣。#[derive(Debug)]structTask{id:u64,title:String}traitRenderer{fnrender(self,task:Task)-String;fnname(self)-staticstr{renderer}}structPlainText;structJsonLine;implRendererforPlainText{fnrender(self,task:Task)-String{format!(#{} {},task.id,task.title)}fnname(self)-staticstr{plain}}implRendererforJsonLine{fnrender(self,task:Task)-String{letescapedtask.title.replace(,\\\);format!(r#{{id:{},title:{}}}#,task.id,escaped)}fnname(self)-staticstr{json}}fnshow_all(task:Task,renderers:[BoxdynRenderer]){forrendererinrenderers{println!({}{},renderer.name(),renderer.render(task));}}fnmain(){lettaskTask{id:9,title:实现 trait.into()};letrenderers:VecBoxdynRenderervec![Box::new(PlainText),Box::new(JsonLine)];show_all(task,renderers);}运行输出plain#9 实现 trait json{id:9,title:实现 trait}示例手写 JSON 转义只覆盖双引号用于展示 trait不可作为通用序列化器生产中应使用serde_json正确处理控制字符和 Unicode。trait 的默认方法适合真正通用行为若实现者普遍覆盖说明抽象边界可能不对。四、踩坑把 trait 当传统继承树Rust trait 不存共享字段也没有类层级。优先组合小能力例如Render、Store、Validate不要创建包含几十个方法的“万能服务”trait。只有一个实现且没有测试替身或边界需求时直接用具体类型更清楚过早抽象会增加泛型和生命周期噪声。对象安全决定 trait 能否写成dyn Trait。返回Self、带泛型方法等成员通常不能通过 trait 对象调用因为运行时无法确定具体大小或生成哪份泛型代码。可给这些方法加where Self: Sized或把构造行为移出对象接口。错误信息会指出 dyn compatibility先理解调用场景再改签名。模式匹配时不要随意用_隐藏未来变体。核心状态机应列全变体让新增状态触发编译提醒只有确实同策略的剩余输入才用兜底。公共枚举演进还涉及下游穷尽匹配兼容性库作者可评估#[non_exhaustive]。五、验证检查不变量与替换能力测试应从公共 API 操作任务空标题创建失败待办可完成完成后不能再次取消不可能直接构造非法字段。对渲染 trait 写同一组契约测试确认不同实现都保留任务编号和标题。若要模拟存储内存实现应遵守与真实实现相同的未找到、重复编号语义而不只是“为了测试能跑”。本篇完成了领域对象、状态机和行为边界。下一篇将解释泛型如何复用这些能力、生命周期如何描述引用关系并澄清“生命周期不是让值活得更久”从而为后续线程与异步共享数据做好类型准备。参考来源Rust Book结构体Rust Book枚举与模式匹配Rust BookTraitsRust ReferenceTrait Objects 觉得有用就点个赞 收藏方便回头查阅有疑问直接在评论区留言我看到都会回。 本文属于《Rust 入门实战》系列持续更新关注不迷路。 文章里的代码都能直接跑。想要可直接 clone 的完整工程 配套部署脚本 / 踩坑清单评论一声或发邮件到cj2664qq.com我免费发你。如果你正好在做类似系统、或有工程化难题想找人做也欢迎邮件聊一句——我按实际情况评估能落地的就接单或出方案。评论和邮件都能直接找到我不用跳别的平台。
返回列表