ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Swift 标准库中的主关联类型(Primary Associated Types):SE-0358 全面解读与实践指南

2026/9/23 6:25:46 拓冰建站 浏览量
Swift 标准库中的主关联类型(Primary Associated Types):SE-0358 全面解读与实践指南 文档【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址https://gitcode.com/gh_mirrors/sw/swift-evolution点击查看免费下载导读SE-0358Primary Associated Types in the Standard Library随 Swift 5.7 实现在语言层面的轻量约束语法SE-0346与 Swift 标准库协议体系之间架起了一座桥梁它为Sequence、Collection、Clock等核心协议标记主关联类型primary associated types让开发者能够写出some SequenceInt、some CollectionString这样简洁、可读且可组合的泛型签名。读完本文你将掌握主关联类型的设计动机、标准库各协议的主关联类型选型全貌、四条实用的 API 设计准则以及这一特性对源码兼容性、ABI 与 API 弹性的影响边界。背景从 SE-0346 到 SE-0358SE-0346Lightweight same-type requirements for primary associated types 为 Swift 语言引入了主关联类型概念与轻量约束语法协议可以在声明时通过类似泛型参数列表的语法标记一个或多个主关联类型例如protocol SequenceElement使用方则可以在任何原本允许协议一致性约束的位置直接写出SequenceString或Sequence[Token]编译器会将其脱糖desugar为一致性约束 相同类型same-type需求的组合。典型等价关系如下源自 SE-0346 详细设计// 不透明参数声明SE-0341 之后可用 func sortLines(_ lines: some CollectionString) // 等价于 func sortLinesC : CollectionString(_ lines: C) // 再等价于经典写法 func sortLinesC : Collection(_ lines: C) where C.Element String但 SE-0346 只解决了语法问题如果协议本身没有声明主关联类型使用方依然无法享受这种简写。SE-0358 正是为此而生——它把主关联类型注解引入 Swift 标准库的既有协议使轻量约束语法真正落地可用。动机语法要可用协议必须先声明要让轻量约束语法真正可用标准库内外部的协议定义都需要扩展主关联类型声明。SE-0358 的动机非常直接没有主关联类型注解some Sequence[Token]这类写法就是非法的而一旦标准库完成了注解社区库与业务代码中的自定义协议也有了可参照的实践范式。SE-0346 的动机章节给出了一个典型场景一个语法高亮库希望把复杂的具体返回类型隐藏在不透明返回类型之后同时保留元素类型为[Token]这一关键信息// 隐藏具体类型但丢失了 Element [Token] 的信息 func readSyntaxHighlightedLines(_ file: String) - some Sequence // SE-0346 SE-0358 之后 func readSyntaxHighlightedLines(_ file: String) - some Sequence[Token]同样泛型函数也可以从冗长的where子句中解放出来// 经典写法 func concatenateS : Sequence(_ lhs: S, _ rhs: S) - S where S.Element String // 轻量写法 func concatenateS : SequenceString(_ lhs: S, _ rhs: S) - S四条 API 设计准则如何为主关联类型做选型主关联类型为协议设计增加了一个新维度。对于每一个带关联类型需求的公开协议都需要慎重考虑哪些关联类型如果有的话应标记为主。SE-0358 给出了四条经过标准库实践检验的准则——作者明确表示这些准则基于标准库内部的选型经验尚未有足够多的真实世界案例支撑其上升为通用官方指南但对协议作者而言是很好的起点。准则 1让使用习惯决定设计如果是为既有协议添加主关联类型先审视其现有客户端哪个关联类型在约束中占绝对主导以Sequence为例使用方几乎总是约束Element而Iterator几乎从不出现在where子句中因此Element是不二之选。如果是设计新协议则要思考人们最可能约束哪个类型——有时它甚至不是你原本计划作为关联类型的那个。SE-0358 以 SE-0329Clock, Instant, Duration 中的Clock协议为例做了精彩说明Clock最初只有Instant一个关联类型但实际使用中人们更想约束的是Instant.Duration而非Instant本身。时钟与其瞬时点耦合过紧some ClockContinuousClock.Instant本质上只是ContinuousClock的绕圈子写法而some ClockSwift.Duration能表达所有以物理秒计量流逝时间的时钟这一远更有用的抽象。因此 SE-0329 特意为Clock增加了Duration关联类型以充当主关联类型SE-0358 随后确认了这一选择详见后文选型总表。准则 2考虑使用处的清晰度some FooInt, String这种写法与泛型类型实参共享同一套尖括号语法包括其局限——语言不支持在约束列表中写参数标签因此类型名称本身无法提示其角色。熟悉该协议的人应当能凭直觉正确理解some SequenceInt的含义。最合适的主关联类型候选通常是那些与协议本身存在简单、明显关系的类型。一个实用启发式如果这种关系可以用一个简单介词描述该关联类型往往适合做主CollectionofIntIdentifiablebyStringSIMDofFloatRawRepresentablebyInt32反之角色复杂或高度特异的关联类型通常不适合。例如Numeric的Magnitude偶尔会出现在约束中但它的角色过于微妙、不易一眼看懂——即使对 Swift 数值协议体系烂熟于心的读者也难以确定some NumericInt中Int的含义因此Numeric最终没有标记主关联类型。准则 3并非每个协议都需要主关联类型不要因为能做就做。如果预期没有人会实际约束某个关联类型就没有标记的必要如果存在多个看似同样有用的候选可能最好一个都不选参见准则 2。例如ExpressibleByIntegerLiteral预计不会被写进泛型函数声明因此其唯一的关联类型IntegerLiteral也没有被标记。准则 4每个协议最多一个主关联类型语言允许声明多个主关联类型但 SE-0346 要求使用方在使用轻量语法时必须显式约束全部主关联类型——使用方没有便捷方式表达某个类型不约束只能退回到经典泛型语法部分或完全放弃轻量写法protocol MyDictionaryProtocolKey, Value { associatedtype Key: Equatable associatedtype Value // ... } // 这个函数只要求键为 String 的字典类东西 func twiddle(_ items: some MyDictionaryProtocolString, ???) - Int { ... } // 可行方案一显式补全第二个主关联类型 func twiddleValue(_ items: some MyDictionaryProtocolString, Value) - Int { ... } // 可行方案二退回经典语法 func twiddleT: MyDictionaryProtocol(_ items: T) - Int where T.Key String { ... }当然如果绝大多数客户端确实想同时约束两个类型如Key与Value把它们都标记为主也是合理的。选型总表标准库公开协议的主关联类型全景下表源自 SE-0358 核心章节列出了标准库中所有带关联类型需求的公开协议、提议的主关联类型及其他关联类型。标注 (1)–(4) 的条目参见备选方案与设计取舍一节。协议主关联类型其他关联类型SequenceElementIteratorIteratorProtocolElement--CollectionElementIndex,Iterator,SubSequence,IndicesMutableCollectionElementIndex,Iterator,SubSequence,IndicesBidirectionalCollectionElementIndex,Iterator,SubSequence,IndicesRandomAccessCollectionElementIndex,Iterator,SubSequence,IndicesRangeReplaceableCollectionElementIndex,Iterator,SubSequence,IndicesLazySequenceProtocol-- (1)Element,Iterator,ElementsLazyCollectionProtocol-- (1)Element,Index,Iterator,SubSequence,Indices,ElementsIdentifiableID--RawRepresentableRawValue--RangeExpressionBound--StrideableStride--SetAlgebraElementArrayLiteralElementOptionSet-- (2)Element,ArrayLiteralElement,RawValueNumeric--IntegerLiteralType,MagnitudeSignedNumeric--IntegerLiteralType,MagnitudeBinaryInteger--IntegerLiteralType,Magnitude,Stride,WordsUnsignedInteger--IntegerLiteralType,Magnitude,Stride,WordsSignedInteger--IntegerLiteralType,Magnitude,Stride,WordsFixedWidthInteger--IntegerLiteralType,Magnitude,Stride,WordsFloatingPoint--IntegerLiteralType,Magnitude,Stride,ExponentBinaryFloatingPoint--IntegerLiteralType,FloatLiteralType,Magnitude,Stride,Exponent,RawSignificand,RawExponentSIMDScalarArrayLiteralElement,MaskStorageSIMDStorage--ScalarSIMDScalar--SIMDMaskScalar,SIMD2Storage,SIMD4Storage, ...,SIMD64StorageKeyedEncodingContainerProtocol--KeyKeyedDecodingContainerProtocol--KeyExpressibleByIntegerLiteral--IntegerLiteralTypeExpressibleByFloatLiteral--FloatLiteralTypeExpressibleByBooleanLiteral--BooleanLiteralTypeExpressibleByUnicodeScalarLiteral--UnicodeScalarLiteralTypeExpressibleByExtendedGraphemeClusterLiteral--UnicodeScalarLiteralType,ExtendedGraphemeClusterLiteralTypeExpressibleByStringLiteral--UnicodeScalarLiteralType,ExtendedGraphemeClusterLiteralType,StringLiteralTypeExpressibleByStringInterpolation--UnicodeScalarLiteralType,ExtendedGraphemeClusterLiteralType,StringLiteralType,StringInterpolationExpressibleByArrayLiteral--ArrayLiteralElementExpressibleByDictionaryLiteral--Key,ValueStringInterpolationProtocol--StringLiteralTypeUnicode.Encoding--CodeUnit,EncodedScalar,ForwardParser,ReverseParserUnicodeCodec--CodeUnit,EncodedScalar,ForwardParser,ReverseParserUnicode.Parser--EncodingStringProtocol--Element,Index,Iterator,SubSequence,Indices,UnicodeScalarLiteralType,ExtendedGraphemeClusterLiteralType,StringLiteralType,StringInterpolation,UTF8View,UTF16View,UnicodeScalarViewCaseIterable--AllCasesClockDurationInstantInstantProtocolDuration--AsyncIteratorProtocol-- (3)ElementAsyncSequence-- (3)AsyncIterator,ElementGlobalActor--ActorTypeDistributedActor-- (4)ID,ActorSystem,SerializationRequirementDistributedActorSystem-- (4)ActorID,SerializationRequirement,InvocationEncoder,InvocationDecoder,ResultHandlerDistributedTargetInvocationEncoder-- (4)SerializationRequirementDistributedTargetInvocationDecoder-- (4)SerializationRequirementDistributedTargetInvocationResultHandler-- (4)SerializationRequirement从表中可以清晰读出三条选型规律集合层次协议Sequence到RangeReplaceableCollection统一以Element为主与集合的of语义CollectionofInt完全吻合使用习惯一致、无认知负担单一关联类型的简单协议直接标记IdentifiableID、RawRepresentableRawValue、RangeExpressionBound、StrideableStride、IteratorProtocolElement它们的角色都可以用介词一句话说清数值协议Numeric、BinaryInteger、FloatingPoint等全部不标记因为候选类型Magnitude、Stride、Exponent…要么角色不直观要么多选一困难。范围之外Swift 5.6 起无关联类型需求的公开协议截至 Swift 5.6以下公开协议没有关联类型需求因此不在本提案范围之内Equatable, Hashable, Comparable, Error, AdditiveArithmetic, DurationProtocol, Encodable, Decodable, Encoder, Decoder, UnkeyedEncodingContainer, UnkeyedDecodingContainer, SingleValueEncodingContainer, SingleValueDecodingContainer, ExpressibleByNilLiteral, CodingKeyRepresentable, CustomStringConvertible, LosslessStringConvertible, TextOutputStream, TextOutputStreamable, CustomPlaygroundDisplayConvertible, CustomReflectable, CustomLeafReflectable, MirrorPath, RandomNumberGenerator, CVarArg, Sendable, UnsafeSendable, Actor, AnyActor, Executor, SerialExecutor, DistributedActorSystemError详细设计标准库中的注解形式SE-0358 在标准库源码中的实际注解如下原文档中SetAlgebra一行写作prococol系原文笔误此处保留原样以便对照public protocol SequenceElement public protocol IteratorProtocolElement public protocol CollectionElement: Sequence public protocol MutableCollectionElement: Collection public protocol BidirectionalCollectionElement: Collection public protocol RandomAccessCollectionElement: BidirectionalCollection public protocol RangeReplaceableCollectionElement: Collection public protocol IdentifiableID public protocol RawRepresentableRawValue public protocol RangeExpressionBound public protocol StrideableStride: Comparable public protocol SetAlgebraElement: Equatable, ExpressibleByArrayLiteral public protocol SIMDScalar: ... public protocol ClockDuration: Sendable public protocol InstantProtocolDuration: Comparable, Hashable, Sendable主关联类型列表可以命名协议体内或其继承协议中已声明的关联类型如 SE-0346 中的PersistentSortedMapKey, Value : SortedMap示例Key/Value声明在父协议中省略尖括号列表则协议保持无约束状态写法与旧代码完全兼容。值得注意的是为Clock与InstantProtocol增加主关联类型的同时SE-0329 在协议中新增了associatedtype Duration: DurationProtocol需求——先有可约束的关联类型再谈将其标记为主这正是准则 1为新协议主动设计主关联类型的实践范本。轻量约束语法的可用位置SE-0346 速览SE-0358 使下列位置可以真正写出约束形式源自 SE-0346 详细设计扩展的扩展类型extension CollectionString { ... }≡extension Collection where Element String协议的继承子句protocol TextBuffer : CollectionString { ... }泛型参数的继承子句func sortLinesS : CollectionString(_ lines: S) - S关联类型的继承子句associatedtype Lines : CollectionStringwhere子句中的一致性需求where S.Element : SequenceString不透明参数声明func sortLines(_ lines: some CollectionString)不透明返回类型func transformElementsS : SequenceE, E(_ lines: S) - some SequenceE这是此前where子句无法表达的新能力——不透明返回类型不允许附加where子句具体类型的继承子句struct Lines : CollectionString { ... }等价于显式声明typealias Element String类型别名底层类型typealias SequenceOfInt SequenceInt协议组合成员func takeEquatableSequence(_ seqs: some SequenceInt Equatable) {}需注意存在类型existential暂不支持any CollectionString属于更大规模的特性需要运行时元数据与动态转换支持由单独的提案另行讨论。兼容性与稳定性影响源码兼容性无影响这些新注解本身不改变既有代码的行为——它们只是启用了新语法下的新用法与旧代码完全兼容。ABI 稳定性无影响注解不产生 ABI 影响新能力可以部署回任何旧版本的标准库运行时。API 弹性一旦发布即被锁定这是最需要警惕的一点主关联类型一旦引入不能从协议中移除或重新排序否则会破坏源码兼容性SE-0346 要求使用方总是列出协议定义的全部主关联类型在该限制被解除之前向已有主关联类型的协议追加新的主关联类型同样是源码破坏性变更因此本提案涉及的协议其主关联类型列表在标准库随版本发布后将无法再做任何改动——这正是选型必须极其慎重、并优先遵循宁可少标记也不误标记原则的根本原因。备选方案与设计取舍1惰性集合协议为何留白LazySequenceProtocol与LazyCollectionProtocol按集合层次一致性本应标记Element但实际使用中Elements同样甚至更加值得被轻松约束。两个候选难分伯仲提案决定暂不标记留待积累更多轻量约束语法的实战经验后再议。2OptionSet为何留白OptionSet的Element类型设计上恒等于Self因此RawValue才是实际中最实用的选择。但为了避免潜在混淆提案最终没有为OptionSet标记任何主关联类型。3AsyncSequence与AsyncIteratorProtocol为何推迟从逻辑上讲它们理应以Element为主关联类型但当时正在进行关于为其增加精确错误类型的持续讨论typed throws。如果讨论有果可能希望把潜在的Error关联类型也标记为主。为了避免源码兼容性麻烦这两个协议的主关联类型注解推迟到后续提案。这一推迟在仓库中有明确的后续落点SE-0421GeneralizeAsyncSequence见 proposals/0421-generalize-async-sequence.md最终为AsyncSequence和AsyncIteratorProtocol同时采用了Element与Failure两个主关联类型并基于 Swift 6.0 的可用性门槛支持some AsyncSequenceElement, Never、any AsyncSequenceElement, any Error等写法解决了 SE-0358 遗留的错误类型约束问题。4分布式 Actor 协议为何推迟为分布式 Actor 相关协议声明主关联类型本身是值得做的但为了不与潜在的、能使其更有用的未来语言改进相冲突同样推迟到了后续提案。修订记录要点2022-05-28提案初版。2022-06-22从OptionSet移除主关联类型声明API 准则章节措辞修订不再提议将其纳入官方 Swift API 指南即 SE-0023 API Design Guidelines统一使用轻量约束语法lightweight constraint syntax而非轻量相同类型需求lightweight same-type requirements因为新语法用途不止于表达相同类型约束。实践建议如何为自己的协议采纳主关联类型结合 SE-0358 的准则与标准库的选型结果协议作者可以按以下流程操作盘点现有客户端搜索所有where子句与类型约束中对本协议关联类型的引用频率占比悬殊的候选优先考虑做介词测试能否用of / by / in等介词一句话描述该关联类型与协议的关系不能则谨慎评估使用场景该协议是否会被大量写在泛型函数签名、不透明返回类型中如果不会如ExpressibleByIntegerLiteral直接跳过坚持单主类型除非确信绝大多数客户端要同时约束两个类型否则只标记一个发布即锁定记住主关联类型列表一经发布便不可更改不可移除、不可重排、不可追加任何犹豫都应选择保守方案。总结SE-0358 是 SE-0346 轻量约束语法从语言特性走向日常可用的关键一步它让some SequenceInt、some ClockSwift.Duration成为标准库中的一等公民写法同时通过四条选型准则和大量备选方案的审慎取舍为整个 Swift 生态的协议设计立下了可参照的范式。核心要点可以浓缩为三句话选型由使用习惯驱动清晰度用介词测试把关发布之后不可反悔。如果你正在设计带关联类型的公开协议这份提案连同其后续修订就是最值得对照的设计手册。赞分享文档【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址https://gitcode.com/gh_mirrors/sw/swift-evolution点击查看免费下载相关推荐Swift 轻量级 same-type 约束语法SE-0346primary associated types 与 some SequenceString 实战指南Swift 轻量级 same type 约束语法SE 0346primary associated types 与 some SequenceStrin文档Swift SE-0503 详解为带默认值的关联类型抑制默认一致性Suppressed Associated Types With DefaultsSwift SE 0503 详解为带默认值的关联类型抑制默认一致性Suppressed Associated Types With Defaults 导读文档Sway trait 中的关联类型Associated Types如何声明与实现Sway trait 中的关联类型Associated Types如何声明与实现 在 Sway 中开发库或合约时如果想写一个不预先绑定具体类型的 tra编程语言编译器区块链创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考