首页/新闻资讯/正文详情

Swift 非穷尽枚举(Non-Exhaustive Enums)深度解析:SE-0192 的 frozen 与 @unknown 机制

发布时间:2026/9/23 21:29:25 来源:云帆数科 栏目:资讯中心
Swift 非穷尽枚举(Non-Exhaustive Enums)深度解析:SE-0192 的 frozen 与 @unknown 机制
Swift 非穷尽枚举Non-Exhaustive Enums深度解析SE-0192 的 frozen 与 unknown 机制【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址: https://gitcode.com/gh_mirrors/sw/swift-evolutionSE-0192proposals/0192-non-exhaustive-enums.md是 Swift 语言为 ABI 稳定性铺路的关键提案它正式把枚举划分为frozen冻结与non-frozen非冻结两类并引入unknown属性来兼顾穷尽性检查与未来新增 case 的源代码兼容性。本文以该提案为骨架结合仓库中的后续提案SE-0260 library evolution、SE-0487 extensible enums与 Swift 5.0 发布说明releases/swift-5_0.md完整还原该特性的设计动机、语言规则、C 互操作细节、ABI 影响与实战写法帮助你理解为什么你写的unknown default:长这样、它在 Swift 5 之后扮演什么角色。一、为什么要区分 frozen 与 non-frozen 枚举在 SE-0192 之前给一个枚举新增 case 必然破坏源代码兼容性所有穷尽匹配该枚举的switch都会因为缺少新 case 而编译失败。这显然与 Apple 持续演进 SDK API 的流程相悖。例如 iOS 10 中Foundation 的DateComponentsFormatter.UnitsStyle新增了briefcaseUIKit 的UIKeyboardType新增了asciiCapableNumberPad大型错误枚举也会随库支持的新操作而增长。库作者必须拥有在不破坏二进制兼容的前提下为枚举增加 case的能力。与此同时Swift 开发者非常依赖对枚举做穷尽switch它能防止漏处理分支也使得确定初始化definitive initialization可以在没有default的情况下被编译器强制执行。因此我们不能取消所有 case 已知的枚举而必须显式区分两类枚举frozen冻结枚举保证永远不会新增 case客户端可以穷尽匹配non-frozen非冻结枚举未来可能新增 case客户端必须提供兜底分支。提案作者对 macOS SDK 中 Foundation 公开头文件的调查佐证了这种划分的必要性约 60 个NS_ENUM中仅有 6 个被明确期望做穷尽匹配——ComparisonResult、NSKeyValueChange/NSKeyValueSetMutationKind、NSRectEdge、FileManager.URLRelationship、以及可能算上的Decimal.CalculationError。也就是说Objective-C 公开枚举的默认形态就应该是可能增长。术语说明语法上是 frozen / non-frozen 而非 unfrozen因为 unfrozen 暗示它曾经被冻结过。二、核心设计Swift 4.2 / 5.0 中的默认行为提案在 Swift 4.2 中落地了如下默认规则从 C 导入的枚举、标准库与 overlay 中定义的枚举被分为 frozen 与 non-frozen 两类客户端 switch 一个 non-frozen 枚举时必须包含某种兜底 casedefault、case _等Swift 5 模式下遗漏兜底 case 会产生警告根据提案被接受后的修订原本计划是错误后降级为警告Swift 4 模式下则完全没有诊断除标准库与 overlay 之外、用 Swift 写的所有枚举在 Swift 4.2 中隐式视为 frozen从 C 导入的枚举默认视为 non-frozen可通过新增的 C 侧注解改为 frozen。关键点在于只有switch的穷尽性检查受到 frozen/non-frozen 区分的影响。if case、枚举构造、访问成员等其他用法一概不变。对 frozen 枚举以及布尔值做非穷尽 switch 在所有语言模式下仍然是非法的。2.1 非穷尽 switch 的编译行为switch excuse { case .eatenByPet: // … case .thoughtItWasDueNextWeek: // … }在 Swift 5 中上面的代码会因缺少兜底 case 而产生警告如果运行时真的遇到未知 case程序会直接trap崩溃。更复杂的例子同样适用switch (excuse, notifiedTeacherBeforeDeadline) { case (.eatenByPet, true): // … case (.thoughtItWasDueNextWeek, true): // … case (_, false): // … }这个 switch 覆盖了所有已知模式但并未考虑第二个元组元素为true时出现新枚举 case的可能性因此在 Swift 5 中同样会产生警告。三、unknown属性在兜底与穷尽检查之间取得平衡用普通default兜底有一个明显的副作用编译器再也无法提醒开发者某个枚举有未被显式处理的元素。为此SE-0192 为 switch case 引入了新属性unknown。3.1 基本写法switch excuse { case .eatenByPet: // … case .thoughtItWasDueNextWeek: // … unknown default: // … }unknown default与普通default一样匹配任何值是兜底 case。区别在于如果枚举的所有已知元素尚未全部匹配编译器会产生警告而非错误。选择警告而非错误是为了让给枚举新增元素继续保持源代码兼容。这同时也是unknown default匹配任意值而非仅匹配编译期未知的值的原因。提案接受时做了一处关键修订最初的 unknown:新 case 写法被改为unknown属性且只能应用于default:与case _:。两种拼写unknown default:与unknown case _:均被接受。3.2 使用限制与告警规则unknown只能应用于default或由单一_模式构成的 case即使用于case _unknown也必须位于switch的最后一个 case如果被匹配的模式中所有枚举都被显式标注为 frozen或者模式中根本没有枚举编译器会警告unknown没有意义同样是警告而非错误以便把枚举标注为 frozen保持源代码兼容如果模式中包含隐式 frozen 的枚举例如用户自定义的 Swift 枚举unknown依然被允许以方便代码为未来新增的 case 做准备。3.3unknown不可测试但可与fallthrough组合unknown存在一个天然缺陷它无法被测试——你无法构造一个不匹配任何已知 case的枚举值即便构造出来也没有安全的使用方式。但将unknown与其他 case 组合fallthrough既可以复用另一分支的行为又能继续获得新 case 到来时编译器提醒的好处switch excuse { case .eatenByPet: showCutePicturesOfPet() case .thoughtItWasDueNextWeek: fallthrough unknown default: askForDueDateExtension() }四、C 枚举与enum_extensibilityC 导入的枚举存在一个麻烦很难判断它属于当前项目还是外部 SDK。NS_ENUM位于 Apple SDK 中时应当视为 non-frozen但位于你自己的框架中时可能应该是 frozen——即便那样还可能出现定义在.m文件里的私有 case// MyAppPaperSupport.h typedef NS_ENUM(NSInteger, PaperSize) { PaperSizeUSLetter 0, PaperSizeA4 1, PaperSizePhoto4x6 2 };// MyAppPaperSupport.m static const PaperSize PaperSizeStickyNote 255;这种模式在 Apple SDK 中确实存在虽然不常见。因此从 C 导入的枚举采取保守策略未标注的NS_ENUM一律按 non-frozen 导入并在所有上下文中如此对待。新增的 C 属性enum_extensibility可覆盖此行为typedef NS_ENUM(NSInteger, GregorianMonth) { GregorianMonthJanuary 1, GregorianMonthFebruary, GregorianMonthMarch, GregorianMonthApril, GregorianMonthMay, GregorianMonthJune, GregorianMonthJuly, GregorianMonthAugust, GregorianMonthSeptember, GregorianMonthOctober, GregorianMonthNovember, GregorianMonthDecember, } __attribute__((enum_extensibility(closed)));注意enum_extensibility(closed)或enum_extensibility(open)的存在会让 Swift 把该类型视为真枚举true enum此前唯一的途径是使用NS_ENUM/CF_ENUM宏。新增的flag_enumC 属性则用于标识NS_OPTIONS这类选项集合。除影响 switch 之外frozen C 枚举的init(rawValue:)还会强制要求传入的是编译期已知的 casenon-frozen 导入枚举则继续不对原始值做任何检查。五、标准库与 overlays 中的 frozen 清单多数标准库枚举不需要非冻结带来的灵活性因此被标记为 frozen❄️ClosedRange.Index❄️FloatingPointSign❄️FloatingPointClassification❄️Never❄️Optional❄️UnicodeDecodingResult❄️Unicode.ParseResult以下标准库公开枚举不标记为 frozenDecodingErrorEncodingErrorFloatingPointRoundingRuleMirror.AncestorRepresentationMirror.DisplayStylePlaygroundQuickLook本已弃用overlay 虽不严格属于 Swift 开源项目由 Apple 框架团队维护但提案给出的暂定计划是ARCamera.TrackingStateon / off / limited(Reason) 三态与DispatchTimeoutResultsuccess / timed out标记为 frozen其余公开枚举保持 non-frozen包括Calendar.Component、Calendar.Identifier、Calendar.MatchingPolicy、Data.Deallocator、DispatchTimeInterval、JSONDecoder.DateDecodingStrategy、JSONEncoder.KeyEncodingStrategy、MachErrorCode、POSIXErrorCode等 20 余个。对使用 Swift 的日常开发者而言这些清单意味着一个直观体验对Calendar.Component这类 SDK 枚举写 switch 时编译器会要求你写unknown default:。六、与其他语言设计的横向对比提案专门调研了其他语言对枚举可增长问题的处理方式可分为三类没有 non-frozen 枚举Haskell、OCaml新增 case 总是源代码破坏性变更它们也不太在意二进制兼容、Kotlinenum class 用得较少、C#官方文档承认语言对此帮助有限、Objective-C但 Apple 正在通过enum_extensibilityClang 属性探索。替代设计F# 的 union 要么全部暴露 case 要么完全不暴露相当于 Swift 中禁止 switch 该枚举D 语言区分switch与final switch只有后者要求穷尽——这是使用侧的决策而非定义侧Scala 更常用sealed traitsSwift 术语里近似所有遵循类型都已知的协议。与本次提案相似的设计Rust 有一个非常类似的 non-exhaustive 枚举提案但为不破坏既有 Rust 程序frozen 仍是默认。七、源代码兼容性规则与破坏契约提案确立了明确的源代码兼容性矩阵变更源代码兼容给 non-frozen 枚举新增 caseC 导入或标准库定义✅ 兼容给 frozen 枚举新增 case❌ 不兼容从公开枚举无论 frozen 与否移除 case❌ 仍不兼容non-frozen → frozen✅ 兼容frozen → non-frozen❌ 不兼容破坏契约的后果同样有明确定义若库作者给 frozen 枚举新增 case所有未处理该 case 的既有 switch即没有default或_模式的会得到与 Swift 4 中非穷尽 switch相同的错误若库作者把原本 frozen 的枚举改为 non-frozen任何缺少兜底 case 的 switch 会产生警告。八、对 ABI 稳定性与 Library Evolution 的影响8.1 布局与间接寻址non-frozen Swift 枚举的布局绝不能暴露给客户端——库可能在下一版本新增一个装不下的 case。这导致该类枚举出现在公开 API 中时需要额外的间接层而 frozen 枚举的布局仍对客户端开放供优化使用。此变更不影响objc枚举的布局无论从 C 导入还是在 Swift 中定义非objc枚举的 case 表示可能与其 raw value 不同这能提高所有 case 在编译期已知时switch的执行效率。8.2 二进制兼容规则给 non-frozen 枚举新增 case 现在是二进制兼容变更从公开枚举移除 case 仍然不是二进制兼容变更给枚举添加或移除objc都不是二进制兼容变更把 non-frozen 枚举改为 frozen 是希望未来在不破坏二进制兼容的前提下支持的目前尚无设计反向操作则被禁止。8.3 破坏 ABI 契约的严重性编译器依据 frozen 枚举的 case 集合决定其内存表示与调用约定因此给 frozen 枚举新增 case、或将其标记为 non-frozen会令未重新编译的客户端应用陷入未定义行为——其内存安全与类型安全损失可与误用 unsafe 类型相提并论最可能表现为崩溃但也可能使代码被意外执行或跳过。作为特例对objc枚举无论导入还是 Swift 定义switch 到意外值总是 trap 而非未定义行为即使该枚举是 frozen 的。8.4 与 SE-0260 的衔接SE-0260proposals/0260-library-evolution.md把这一概念扩展到了全部 struct/enum开启-enable-library-evolution后库中类型的默认行为变为 resilient非 frozen并通过frozen属性按类型逐一退出这种弹性。该提案明确指出枚举的默认行为将改为 non-frozen这是库使用者唯一可见的变化——他们需要使用 SE-0192 描述的unknown default:技巧见 proposals/0260-library-evolution.md。同时标记枚举为frozen将恢复库使用者不带unknown default:穷尽 switch 的能力因为它保证了不再新增 case见 proposals/0260-library-evolution.md。换句话说SE-0192 定义了非冻结时客户端怎么写SE-0260 则定义了冻结如何声明。九、未来方向提案中的前瞻标准库之外的非 frozen Swift 枚举早期版本曾为所有公开 Swift 枚举引入 frozen/non-frozen 区分核心团队认为这仅对存在二进制兼容诉求的库有价值需要更成熟的版本化概念应单独成案——这一方向后来由 SE-0487见下文部分落地。unknown模式理论上可以设计一种新模式在更大的模式如元组内部匹配未知 case例如case (#unknown, true):。但这会产生前一个已知 case 抢走匹配的意外结果且unknown只能放在最后一个 case 的限制无法推广到任意模式故未纳入本提案。与其他兜底 case 组合目前unknown只支持default:与case _:case let value:、case (_, let b):等兜底形态暂不支持但无技术障碍。非公开 casenon-frozen 枚举的实现机制同时允许公开枚举存在非公开 caseApple SDK 中已有实践建议未来frozen 枚举不得含非公开 case。兼容性检查通过比较各版本 swiftmodule 的 API 检查器、或把类型布局编码进符号名客户端链接该符号布局变化即启动失败防止库作者误给 frozen 枚举加 case。带 raw type 的高效表示曾考虑用 32 位整数表示无载荷枚举40 亿个 case 是合理上限但这会让增删 raw type 成为 ABI 破坏性变更并使下面两种写法不再等价故超出范围/* non-frozen */ public enum HTTPMethod: String { case get GET case put PUT case post POST case delete DELETE }/* non-frozen */ public enum HTTPMethod: RawRepresentable { case get case put case post case delete public init?(rawValue: String) { switch rawValue { case GET: return .get case PUT: return .put case POST: return .post case DELETE: return .delete default: return nil } } public var rawValue: String { switch self { case .get: return GET case .put: return PUT case .post: return POST case .delete: return DELETE } } }十、备选方案回顾为什么最终是unknown提案用了大量篇幅讨论备选方案理解它们有助于把握设计取舍术语之争closed/open 因与类的open语义冲突被否决exhaustive/non-exhaustive、final/non-final、sealed/non-sealed 等十余个候选complete/incomplete、covered、non-extensible、fixed、locked、total/partial……都被讨论过最终采纳 Becca Royal-Gordon 建议的frozen。unknown命名之争候选拼写包括future:、unexpected:、undeclared:、unknown case:、unknown default:、unused default:、runtimeReachableOnly default:、default unknown:、default(unknown):、fallback:、invisible:等。核心团队最终选定unknown default:/unknown case _:——因为它不被绑定到default上。switch!一个在遇到未知 case 时只能 trap、不支持其他动作的替代语法与明知未来会有新 case的非冻结枚举精神不符。测试无效 case用testable注解允许创建无效枚举值、配合#invalid表达式测试未知 case 的处理逻辑但因无法把无效值传回原库而搁置属附加特性可后续补。允许源码包中的枚举视为 non-frozen第一版提案曾覆盖所有公开枚举核心团队认为对不关心该能力的用户而言代价大于收益予以否决。去掉unknown初版提案只允许普通default但社区强烈不满穷尽性检查的丧失最终保留。unknown与其他兜底 case 混用如unknown case _:后接普通case _:同一段代码在重编译前后行为会变化故被禁止。引入新的声明种类如choices HomeworkExcuse { … }增加语言表面积且易让作者误用不如将 frozen/non-frozen 视为同一声明种类的两个变体。改用协议协议能模拟 non-frozen 枚举的全部能力但失去了unknown的穷尽性检查、也无法禁止他人添加case且需重写既有代码。把 non-frozen C 枚举导入为 RawRepresentable struct不能解决未来 Swift 库的问题还要求大量项目改写 switch。让 Apple 别再给 C 枚举加 case不可能这是 Apple 框架的既定模式。十一、实战小结现代 Swift 中你应该怎么写结合 SE-0192、SE-0260 与后续演进当前 Swift 的实践要点可归纳为对 SDK / 库导入的 non-frozen 枚举写 switch 时始终保留unknown default:或unknown case _:必须放在最后一个 case既满足穷尽性检查又让未来新增 case 只产生警告而非运行时崩溃或编译错误对自己库中的公开枚举若追求 ABI 稳定并可能新增 case默认保持 non-frozenresilient让客户端使用unknown default:若确认永不变更如Optional、Never这类可显式标注 frozenSE-0260 的frozen换取直接布局与优化给 frozen 枚举新增 case 是源代码与二进制双重破坏性变更等同于滥用 unsafe 级别的未定义行为风险务必通过 API 检查工具防止误操作unknown分支无法直接测试可通过fallthrough复用已知分支的行为后续 SE-0487proposals/0487-extensible-enums.md状态 Implemented进一步将可扩展枚举能力扩展到非 resilient 的普通 Swift 库标志着这条演进路线的延续。SE-0192 的核心结论可以概括为一句话给会变的枚举一个明确的身份并把可能变写进编译器的检查规则里——这正是 Swift 在保持穷尽匹配这一核心语言体验的同时得以支撑 ABI 稳定与库演进的基石。【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址: https://gitcode.com/gh_mirrors/sw/swift-evolution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

Apache Druid 空间索引与空间过滤器(Spatial Filter)实战指南
Apache Druid 空间索引与空间过滤器(Spatial Filter)实战指南

数据库OLAP大数据后端 【免费下载链接】druid Apache Druid: a high performance real-time analytics database. 项目地址: https://gitcode.com/gh_mirrors/druid6/druid 点击查看 免费下载 本文围绕 Apache Druid 原生查询语言中的空间过滤能力展开,… · 2026/9/23 21:29:25

Nextion串口屏驱动安装与中文固件刷写:MMDVM热点显示恢复实战
Nextion串口屏驱动安装与中文固件刷写:MMDVM热点显示恢复实战

简介:业余无线电数字语音通信中,MMDVM热点板配合Nextion串口屏的用途很广,但不少HAM在安装驱动或刷入中文固件时频频失败。这份资料正是针对该痛点的操作指南,适合已能进入Pi-Star配置页面、想为STM32-DVM热点完善屏幕显示的入门进… · 2026/9/23 21:29:25

开题报告怎么写:从选题表到任务书的完整流程
开题报告怎么写:从选题表到任务书的完整流程

开题报告怎么写:从选题表到任务书的完整流程 在毕业季临近时,许多学弟学妹们常常感到焦虑,尤其是在填选题表、撰写毕业设计任务书和开题报告时。尤其是选题表即将截止,任务书的字段空白,开题报告还未展开的情况下&… · 2026/9/23 21:29:25

贝叶斯与MLE在医疗AI中的实战落地:先验、后验与全概率的工程化解读
贝叶斯与MLE在医疗AI中的实战落地:先验、后验与全概率的工程化解读

1. 这四个概念不是数学考试题,而是你每天都在用的决策引擎先验概率、后验概率、全概率公式、贝叶斯公式、最大似然估计——这串术语一出来,很多人第一反应是翻出大学概率论课本,或者点开某在线课程的“第7章:贝叶斯推断”。但我想… · 2026/9/23 22:10:11

vLLM-Omni 新 TTS 模型怎么接?从跑通第一句音频到合入主线的完整实战路径
vLLM-Omni 新 TTS 模型怎么接?从跑通第一句音频到合入主线的完整实战路径

vLLM-Omni 新 TTS 模型怎么接?从跑通第一句音频到合入主线的完整实战路径 【免费下载链接】vllm-omni A framework for efficient model inference with omni-modality models 项目地址: https://gitcode.com/GitHub_Trending/vl/vllm-omni 你手上有一个 Hug… · 2026/9/23 22:10:11

EmDash CLI 内容编辑流程深度指南:Portable Text 转换、`_rev` 并发令牌与自动发布机制
EmDash CLI 内容编辑流程深度指南:Portable Text 转换、`_rev` 并发令牌与自动发布机制

CMS后端前端插件系统 【免费下载链接】emdash EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress 项目地址: https://gitcode.com/gh_mirrors/emdas/emdash 点击查看 免费下载 EmDash(templates/marketing-… · 2026/9/23 22:10:05

食管癌多模态生存预测系统:Python实现DICOM/WSI/CSV对齐与可解释风险建模
食管癌多模态生存预测系统:Python实现DICOM/WSI/CSV对齐与可解释风险建模

简介:本资源是一套基于Python实现的食管癌多模态健康与生存预测系统源码,面向医学AI研究者、生物信息学开发者及具备Python基础的临床科研人员,旨在解决食管癌患者预后评估中影像与临床数据融合建模难、特征可解释性弱等实际问题。压缩包共22… · 2026/9/23 22:10:05

机械工程控制基础课件制作:从传递函数到仿真配图的完整路径
机械工程控制基础课件制作:从传递函数到仿真配图的完整路径

简介:这是一份面向机械工程及相关专业学生和初学者的《机械工程控制基础》课程PPT,源自三峡大学机械与材料学院方子帆教授的课堂讲义,聚焦控制理论的基本概念、系统工作原理与组成,并通过恒温箱温度控制、钢铁轧制等案例讲解自动控… · 2026/9/23 22:09:58

美赛特等奖论文的建模思维解剖与工程化复用
美赛特等奖论文的建模思维解剖与工程化复用

简介:本资源为2021年美国大学生数学建模竞赛(MCM)特等奖论文合辑,面向数学建模参赛者、高校理工科学生及科研入门者,提供高水准建模思路、跨学科方法融合与完整赛题解决方案的权威范本。合辑以一篇聚焦真菌分解过程的特… · 2026/9/23 22:09:52

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码