当前位置: 首页 > news >正文

【Rust中级教程】2.10. API设计原则之受约束性(constrained) Pt.1:对类型进行修改、`#[non_exhaustive]`注解

2.10. API设计原则之受约束性(constrained) Pt.1:对类型进行修改

2.10.1. 接口的更改要三思

如果你的接口要做出对用户可见的更改,那么一定要三思而后行。

你需要确保你做出的变化:
- 不会破坏现有用户的代码
- 这次变化应该保留一段时间

频繁推送向后不兼容的更改(主版本增加),会导致用户的不满。

2.10.2. 向后不兼容的更改

有些向后不兼容的更改是显而易见的,比如说你改变公共类型的名称,或从中移除一个公共项。

有些向后不兼容的更改则很微妙,这与Rust的工作方式息息相关。这篇文章主要讲的就是这种更改,以及你作为开发者应该如何为其制定修改计划。

在这个过程中,有时候你就需要在接口的灵活性上做出权衡与妥协。

2.10.3. 对类型进行修改

如果你移除或重命名一个公共类型几乎肯定会破坏用户的代码,解决办法就是尽可能利用可见性修饰符。比如说:
-pub(crate):对当前这个crate可见
-pub(in path):对指定的路径可见

看个例子:

pub mod outer_mod { pub mod inner_mod { // 该函数仅对 `outer_mod` 可见 pub(in crate::outer_mod) fn outer_mod_visible_fn() {} // 该函数对整个 crate 可见 pub(crate) fn crate_visible_fn() {} // 该函数仅对 `outer_mod` 可见(使用 `super` 指向外部模块) pub(super) fn super_mod_visible_fn() { // 由于 `inner_mod_visible_fn` 在相同模块内可见,可以正常调用 inner_mod_visible_fn(); } // 该函数仅对 `inner_mod` 内部可见,相当于 `private` pub(self) fn inner_mod_visible_fn() {} } pub fn foo() { inner_mod::outer_mod_visible_fn(); inner_mod::crate_visible_fn(); inner_mod::super_mod_visible_fn(); // 该函数不再可见,因为我们已经在 `inner_mod` 之外 // Error! `inner_mod_visible_fn` 是私有的 inner_mod::inner_mod_visible_fn(); } } fn bar() { // 这个函数仍然可见,因为我们在同一个 crate 内 outer_mod::inner_mod::crate_visible_fn(); // 这个函数在 `outer_mod` 之外不再可见 // Error! `super_mod_visible_fn` 是私有的 outer_mod::inner_mod::super_mod_visible_fn(); // 这个函数在 `outer_mod` 之外也不可见 // Error! `outer_mod_visible_fn` 是私有的 outer_mod::inner_mod::outer_mod_visible_fn(); outer_mod::foo(); }

inner_mod模块中函数的可见性控制:
-outer_mod_visible_fn():仅在outer_mod内部可见,外部无法访问。
-crate_visible_fn():整个crate可见,即bar()仍然可以访问它。
-super_mod_visible_fn():outer_mod内部可见bar()无法访问
-inner_mod_visible_fn():私有,仅inner_mod内部可见

你写的API中公共类型越少,更改时就越自由(自由指保证不会破坏现有代码)。


#[non_exhaustive]注解

用户的代码不仅仅通过名称依赖于你的类型。看个例子:

一个破坏性变更的例子

最开始在lib.rs中我写了一个结构体名叫Unit

pub struct Unit;

然后我在main.rs中使用了Unit

fn main() { let u = constrained::Unit; }
  • 这没有任何问题。

后来呢,我对Unit进行了一些修改,因为用户要用:

pub struct Unit { pub field: bool, }

在main.rs中代码也会变:

fn is_true(u: constrained::Unit) -> bool { matches!(u, constrained::Unit { field: true }) } fn main() { let u = constrained::Unit { field: true, }; }
  • is_true这个函数用到了修改后Unit的字段
  • 但是main函数中本来的代码就会报错

这种情况也会在Unitfield是私有字段时发生。因为编译器知道Unit有字段,而你没有填写这个字段的值。


解决方案

针对这种情况,Rust提供了#[non_exhaustive]注解来缓解这些问题。它可以引用于structenumenum的变体。这个注解表示类型或枚举在将来可能会添加更多字段或变体。

如果你使用了它,那么别人在使用你的crate时,编译器会:
- 禁止显式的构造,比如:lib::Unit { field: true }
- 禁止非穷尽模式的匹配(即没有尾随..的模式)

如果你的接口比较稳定,就应该避免使用这个注解。

看例子:

lib.rs:

#[non_exhaustive] pub struct Config { pub window_width: u16, pub window_height: u16, } fn some_function() { let config: Config = Config { window_width: 640, window_height: 480, }; // Non-exhaustive structs can be matched on exhaustively within the defining crate. if let Config { window_width, window_height, } = config { // ... } }
  • 标注了#[non_exhaustive],lib.rs里仍然可以使用显式的构造,仍然可以使用穷尽模式的匹配,因为这些代码与定义这个结构体的代码属于同一crate之内

那么我在main.rs这么写呢:

use constrained::Config; fn main() { let config: Config = Config { window_width: 640, window_height: 480, }; if let Config { window_width, window_height, } = config {} }
  • 这样写就会报错,因为这里的代码属于外部crate,编译器就会禁止上面所说的两种操作

输出:

error[E0639]: cannot create non-exhaustive struct using struct expression --> src/main.rs:4:26 | 4 | let config: Config = Config { | __________________________^ 5 | | window_width: 640, 6 | | window_height: 480, 7 | | }; | |_____^ error[E0638]: `..` required with struct marked as non-exhaustive --> src/main.rs:9:12 | 9 | if let Config { | ____________^ 10 | | window_width, 11 | | window_height, 12 | | } = config {} | |_____^

我们可以稍微改一下代码使main.rs中的匹配变成带..的非穷尽匹配:

if let Config { window_width, window_height, .. // 它用于忽略结构体、元组或枚举中的其余字段或变体 } = config {}
http://www.jsqmd.com/news/1256563/

相关文章:

  • 2026 年当下,湖州有实力的衣橱整理收纳培训供货商哪个好,扔掉后悔!衣橱焕新自救指南-娘子帮母婴 - 品质体验官
  • 目标检测:视觉系统中的CNN-Transformer融合网络
  • Python基础5-18
  • python元组知识点
  • 游戏客服响应效率提升300%:揭秘头部厂商AI机器人背后的真实训练数据与对话引擎架构
  • 抖音下载神器:如何用免费工具一键批量下载无水印视频?
  • 5大核心技术构建高效抢票自动化系统:从原理到实战的完整指南
  • AI数字人直播月入10万+的5个真实案例:从零搭建到稳定变现的全流程拆解
  • 阿拉善出发西藏年度口碑榜:2026纯玩小团冠军揭晓,这家15年地接社凭什么?| 附:旅行社电话 - 西藏康泰旅行社
  • Three.js 城市混合Shader教程
  • 3步解锁网易云音乐NCM文件:ncmdumpGUI完整使用指南
  • Micrometer 系列【4】入门案例
  • Python 项目配置管理:用 pydantic-settings 管理 RAG 服务的多环境配置
  • 如何彻底移除Windows Defender:系统管理员终极指南与性能优化工具
  • day8
  • 电瓶车托运专线物流哪家靠谱?慧寄侠整车直发不拆电池260元起 - 快递物流资讯
  • svn背景颜色和符号,图解详细说明
  • 03-Kmeans-顾客数据聚类分析
  • Linux调试方法
  • 【MQTT】paho.mqtt.c 库的“介绍、下载、交叉编译” 详解,以及编写MQTT客户端例子源码
  • 抖音下载神器:如何一键批量下载无水印视频的完整指南
  • springboot民宿预约管理系统---附源码42075
  • 2026 年海晏专业的消防专用防冻液公司联系方式,零下30℃天里,消防官兵靠啥守住供水生命线?这玩意儿比普通防冻液靠谱10倍 - 企业推荐官【认证官方】
  • 【Rust中级教程】2.9. API设计原则之显然性(obvious) :文档与类型系统、语义化类型、使用“零大小”类型
  • 鸿蒙 PC Markdown 编辑器系统剪贴板权限与中文回归
  • 零基础5分钟搭建:如何用daily_stock_analysis实现A股智能分析
  • 百奥几何:AI for Science 浪潮下的中国先锋,如何以专业模型重塑生物制药未来?
  • SCMP供应链管理专家考试地点 - 众智商学院职业教育
  • Formality:比较点的验证状态和整体验证状态
  • HarmonyOS ArkTS 实战:实现一个校园表白墙与树洞应用