ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SE-0465 深度解读:Swift 标准库为非可逃逸类型(Nonescapable Types)提供基础原语支持

SE-0465 深度解读:Swift 标准库为非可逃逸类型(Nonescapable Types)提供基础原语支持 SE-0465 深度解读Swift 标准库为非可逃逸类型Nonescapable Types提供基础原语支持【免费下载链接】swift-evolutionThis maintains proposals for changes and user-visible enhancements to the Swift Programming Language.项目地址: https://gitcode.com/gh_mirrors/sw/swift-evolution导读本文基于 Swift 官方演进提案 SE-0465Standard Library Primitives for Nonescapable TypesSwift 6.2 已实现展开系统讲解 Swift 标准库如何进一步拥抱~Escapable类型体系让Optional、Result可以包裹非可逃逸值让MemoryLayout能查询非可逃逸类型的布局信息并新增extendLifetime()生命周期管理原语。读完本文你将理解非可逃逸类型的隐式生命周期推断规则、标准库各 API 的泛化签名与条件约束以及这些改动背后的源码/ABI 兼容性考量从而能在自己的 API 设计中正确使用Span等非可逃逸类型。一、背景与动机从~Copyable到~Escapable的第二次泛化Swift 的所有权控制路线图经历了两个阶段。第一阶段由 SE-0437Noncopyable Standard Library PrimitivesSwift 6.0 实现开启它为Optional、Result、MemoryLayout、UnsafePointer家族等标准库基础类型引入非可拷贝~Copyable支持第二阶段是 SE-0446Nonescapable Types引入的非可逃逸~Escapable类型——这类值可以被局部拷贝但不能被赋值或转移到当前上下文之外为Span这类借用他人存储的安全高性能类型奠定了基础。SE-0465 正是这两条线的交汇点它延续 SE-0437 的工作把泛化范围进一步扩展到非可逃逸维度目标是允许Optional包裹非可逃逸类型并让Optional自身变为条件可逃逸对Result做同样处理使其success分支可持有非可逃逸值泛化MemoryLayout允许查询非可逃逸类型的内存布局基础信息继续泛化生命周期管理函数并引入不带闭包参数的新函数extendLifetime()允许为不可拷贝/不可逃逸元类型生成ObjectIdentifier允许对不可拷贝/不可逃逸元类型做相等比较。此外还修正了 SE-0437 遗留的几处与可拷贝性相关的遗漏ManagedBufferPointer的Equatable一致性、Unsafe[Mutable]BufferPointer.indices的通用化、以及缓冲区指针切片Slice上若干操作的泛化。相关讨论可参考 SE-0446、SE-0447Span与 SE-0456标准库类型的span属性。需要强调的是本提案的一切改动都以最小破坏为原则现有代码隐式假定可拷贝与可逃逸它必须继续像以前一样工作。二、非可逃逸的Optional2.1 三种构造非空非可逃逸可选值的方式Optional需要支持包裹所有 Swift 类型无论其是否可拷贝、是否可逃逸。这意味着Optional必须变为条件可逃逸当它包裹非可逃逸类型时自身也是非可逃逸的且其生命周期约束必须与被包裹的值完全一致——把非可逃逸值包进可选值绝不能让它逃出预期上下文。Swift 构造可选值有多种方式。给定一个非可选的Span值以下代码演示了三种基本的非空非可逃逸可选值构造方式func sample(_ span: SpanInt) { let a Optional.some(span) // OK显式 case 工厂 let b: Optional span // OK隐式可选提升 let c Optional(span) // OK显式初始化器调用 }a、b、c持有同一个 span 实例其生命周期受制于与原始 span 相同的约束——它们可以在sample函数上下文内使用但不能逃逸到函数之外除非将来引入显式的生命周期依赖标注。2.2 三种构造空可选值的方式同样也必须能构造不持有任何内容的空Optional。三种基本方式全部被泛化以支持不可拷贝的包裹类型func sample(_ span: SpanInt) { var d: SpanInt? .none // OK显式 case 工厂 var e: SpanInt? nil // OKnil 字面量表达式 var f: SpanInt? // OK隐式 nil 默认值 }空的可选值技术上仍是非可逃逸的但它天然不绑定任何上下文——空可选值生来具有不朽immortal或称 static生命周期即没有生命周期依赖因此被允许在 Swift 程序的整个执行期间存在。nil 可选值可以传给任何接受非可逃逸可选的函数也可以从任何返回非可逃逸可选的函数返回不过 Swift 目前还没有稳定的方式来定义这类函数。2.3 变量重新赋值与生命周期依赖的变化局部变量的重新赋值被允许任意改变生命周期依赖新旧值的依赖之间不必有任何特定关系——重赋值可以自由地收窄或放宽依赖。例如下面的代码把一个可选变量初始化为不朽的 nil随后赋给它一个具有明确生命周期约束的值最后又把它改回不朽的 nilfunc sample(_ span: SpanInt) { var maybe: SpanInt? nil // immortal maybe span // 明确的生命周期 maybe nil // 再次 immortal }把span赋给maybe不算逃逸因为即使没有后续重赋值局部变量也会在函数返回前被销毁。这种灵活性不一定适用于其他变量种类自定义非可逃逸结构体中的存储属性、全局变量或计算属性预计会携带无法通过重赋值改变的具体生命周期依赖例如非可逃逸类型的全局变量可能只允许持有不朽值。不过目前提案只对局部变量进行推理。2.4 解包机制全部沿用既有语法可选值如果无法判断是否含值并解包查看内容用途就极其有限。SE-0465 保证以下常见机制全部适用于非可逃逸可选值switch与if case/guard case模式匹配// 变体 1完整模式匹配 func count(of maybeSpan: SpanInt?) - Int { switch maybeSpan { case .none: return 0 case .some(let span): return span.count } } // 变体 2带可选语法糖的模式匹配 func count(of maybeSpan: SpanInt?) - Int { switch maybeSpan { case nil: return 0 case let span?: return span.count } }强制解包!特殊形式及其不安全版本、标准库的unsafelyUnwrapped属性func count(of maybeSpan: SpanInt?) - Int { if case .none maybeSpan { return 0 } return maybeSpan!.count }可选链?特殊形式func count(of maybeSpan: SpanInt?) - Int { guard let c maybeSpan?.count else { return 0 } return c }可选绑定if let/guard letfunc count(of maybeSpan: SpanInt?) - Int { guard let span maybeSpan else { return 0 } return span.count }为避免逃逸违规解包非可逃逸可选值得到的新值具有与原始可选值完全相同的生命周期依赖。这一点适用于所有解包形式包括把关联值绑定到新变量的模式匹配形式如上面的let span——得到的span值始终与它来源的可选值具有相同的生命周期。2.5 与nil比较标准Optional类型支持用传统运算符把可选实例与nil比较无论包裹类型是否遵守Equatable。SE-0437 已为不可拷贝包裹类型泛化了这一机制SE-0465 将其扩展到非可逃逸场景func count(of maybeSpan: SpanInt?) - Int { if maybeSpan nil { return 0 } // OK! return maybeSpan!.count }2.6 暂缓的部分高阶函数与??这组核心功能让非可逃逸可选值可用但还不支持更高级的 API。例如标准Optional.map及类似高阶函数最终希望能操作或返回非可逃逸可选类型func sample(_ maybeArray: ArrayInt?) { // 假设 Array.storage 返回非可逃逸的 Span let maybeSpan maybeArray.map { $0.storage } ... }这些操作需要对生命周期依赖进行精确推理因此必须等到语言提供稳定的生命周期标注语法后才能落地。同理nil 合并运算符??也被推迟其当前定义如下另有返回Optional的变体func ?? T: ~Copyable( optional: consuming T?, defaultValue: autoclosure () throws - T ) rethrows - T要把它泛化到非可逃逸T需要声明返回值的生命周期绑定于左参数生命周期与右参数一个函数结果生命周期的交集——目前无法表达这种约束所以同样推迟到相应语言特性出现之后。三、非可逃逸的ResultResult沿与Optional相同的思路泛化允许其success分支包裹非可逃逸值。目前操作非可逃逸Result主要依赖 Swift 通用的枚举机制switch语句、case 工厂、模式匹配、关联值绑定等。重要的便捷 API如Result.init(catching:)、Result.map在引入正式的生命周期依赖表达方式之前仍需要求可逃逸——但这不妨碍勇于尝试的开发者用Result值定义接口。本提案已能泛化两个方法get()与错误映射工具mapError。func sampleE: Error(_ res: ResultSpanInt, E) - Int { guard let span try? res.get() else { return 42 } return 3 * span.count 9 }与解包Optional类似对非可逃逸Result调用get()返回的值其生命周期要求与原始Result实例完全一致——解包结果这一动作不能让内容逃出预期上下文。四、查询非可逃逸类型的内存布局MemoryLayout本提案泛化enum MemoryLayout使其支持获取非可逃逸类型的布局信息print(MemoryLayoutSpanInt.size) // ⟹ 16 print(MemoryLayoutSpanInt.stride) // ⟹ 16 print(MemoryLayoutSpanInt.alignment) // ⟹ 8返回值当然随目标架构而变化。这些信息的用途目前有限因为不安全指针类型尚未泛化到支持非可逃逸 pointee本提案不包含这部分——但没有任何理由为此推迟布局查询能力。要让指针真正指向非可逃逸类型需要为pointee以及一般的指针解引用赋予精确的生命周期语义并且很可能还需要一种允许开发者不安全地覆盖默认生命周期语义的机制这依赖显式生命周期标注因而推迟到未来提案。五、生命周期管理泛化withExtendedLifetime与新增extendLifetime5.1 继续泛化闭包式 APISE-0465 再次泛化withExtendedLifetime函数族这次支持对非可逃逸值调用let span someArray.storage withExtendedLifetime(span) { span in // someArray 在此闭包运行期间正被活跃借用 } // 到此处someArray 可能已可被修改5.2 新函数extendLifetime去掉闭包参数社区已经依次为withExtendedLifetime泛化了1带类型的 throws、2不可拷贝输入与结果、3非可逃逸输入。反复调整这些 API 日益笨拙——尤其在实际实践中withExtendedLifetime最常以空闭包调用作为防止过早销毁的栅栏。这些函数最初是为非空闭包设计的withExtendedLifetime(obj) { weak var ref obj foo(ref!) }而现在推荐的做法往往是用defer语句 空闭包weak var ref obj defer { withExtendedLifetime(obj) {} } // Ugh foo(ref!)显然这些函数并非为这种普遍实践而设计。为承认并拥抱这一风格提案引入一个新的公开标准库函数单纯地延长所给变量的生命周期func extendLifetimeT: ~Copyable ~Escapable(_ x: borrowing T)于是上面的defer咒语可以改写为更可读的形式// 略有改进的现实 weak var ref obj defer { extendLifetime(obj) } foo(ref!)为避免破坏现有代码本提案不废弃基于闭包的既有函数。引入新函数仍将显著减少未来 Swift 版本继续反复泛化既有函数的必要性例如支持 async 使用或支持非可逃逸结果。六、元类型比较与ObjectIdentifier6.1 元类型相等比较Swift 的元类型目前不遵守Equatable但标准库仍提供实现预期相等关系的顶层与!运算符。此前这些运算符只对Copyable与Escapable类型的元类型生效本提案放宽这一要求print(AtomicInt.self SpanInt.self) // ⟹ false经典运算符支持存在元类型Any.Type新变体还接受广义存在类型let t1: any (~Copyable ~Escapable).Type AtomicInt.self let t2: any (~Copyable ~Escapable).Type SpanInt.self print(t1 ! t2) // ⟹ true print(t1 t1) // ⟹ true6.2 为不可拷贝/不可逃逸类型生成对象标识ObjectIdentifier构造主要用于生成标识类实例的Comparable/Hashable值但它也能标识元类型let id1 ObjectIdentifier(Int.self) let id2 ObjectIdentifier(String.self) print(id1 id2) // ⟹ falseSE-0437 没有泛化这个初始化器。SE-0465 现在允许它对不可拷贝与不可逃逸类型都生效import Synchronization let id3 ObjectIdentifier(AtomicInt.self) // OK不可拷贝输入类型 let id4 ObjectIdentifier(SpanInt.self) // OK不可逃逸输入类型 print(id3 id4) // ⟹ false不可拷贝/不可逃逸类型的对象标识仍是一个普通的可拷贝、可逃逸标识——例如它可以与其他 id 比较、可以被哈希。七、杂项修正Odds and Ends7.1ManagedBufferPointer的可相等性SE-0437 遗漏了泛化ManagedBufferPointer的Equatable一致性。本提案允许即使Element不可拷贝也能比较两个ManagedBufferPointer实例是否相等。Managed buffer pointer 是指针类型——无论它寻址的缓冲区中条目是否可拷贝它自身都可比较。7.2 让indices在 unsafe buffer pointer 上普遍可用SE-0437 把 unsafe buffer pointer 类型的indices属性限制在Element可拷贝的情形。此后 SE-0447 引入的Span携带无条件的indices属性SE-0453 引入的InlineArray也如此。为保持一致缓冲区指针也应无条件提供indices无论Element是否可拷贝。indices对遍历这些集合类型很有用尤其是在标准库推出支持不可拷贝/不可逃逸容器的新迭代模型之前。for i in buf.indices { ... }当然未来仍计划为非可拷贝/非可逃逸容器提供直接的 for-in 循环支持那将是远为灵活的方案indices只是过渡性的权宜之计。7.3 缓冲区指针在Slice上的操作SE-0437 还遗漏了泛化 SE-0370 在标准Slice类型上引入的任何缓冲区指针操作。SE-0465 修正这一遗漏泛化了一批能支持不可拷贝结果元素的操作moveInitializeMemory(as:fromContentsOf:)、bindMemory(to:)、withMemoryRebound(to:_:)和assumingMemoryBound(to:)。Slice本身暂时仍要求Element可拷贝这限制了对其他操作的泛化。这些泛化目前仅限可拷贝维度——指针类型含缓冲区指针未来必然需要支持非可逃逸 pointee但那要等能够精确推理生命周期需求之后。八、详细设计API 签名与隐式生命周期规则8.1 关于占位语法_lifetime的重要说明Swift 目前没有正式方式表达函数非可逃逸结果的生命周期依赖也无法对输入参数设置生命周期约束。在语言获得官方语法之前标准库将用不可广泛使用的不稳定语法定义本提案中的 API。本文沿用提案的说明性替身——假想的_lifetime属性并随文简要解释其含义。_lifetime属性不是真实的它只是教学占位符未来的生命周期标注提案可能会提出也可能不会提出类似语法。一旦 Swift 采用正式语法标准库预期会立即切换过去。8.2 非可逃逸枚举类型的隐式生命周期行为SE-0446 引入了非可逃逸枚举类型的概念其中隐含着一套与枚举交互的主要语言特性case 工厂构造、模式匹配的隐式生命周期规则。要泛化Optional和Result必须先理解这些规则如何作用于带单个非可逃逸关联值的枚举类型用单个非可逃逸关联值构造枚举 case 时得到的枚举值被推断为携带与原始输入完全相同的生命周期依赖对这种枚举 case 做模式匹配时暴露出的非可逃逸关联值被推断为携带与原始枚举完全相同的生命周期依赖。enum FooT: ~Escapable { case a(T) case b } func test(_ array: ArrayInt) { let span array.span let foo Foo.a(span) // (1) switch foo { case .a(let span2): ... // (2) case .b: ... } }语句 (1) 中foo隐式拷贝了span的生命周期依赖两个变量都不能逃出test函数体语句 (2) 中对.a模式匹配的 let 绑定创建的span2与foo具有完全相同的生命周期依赖。带多个非可逃逸关联值的枚举 case 的隐式语义此处不描述因为它与Optional、Result均无关。8.3Optional语法糖的隐式生命周期行为Optional枚举附带大量直接内建在语言中的记法便捷机制。本提案为它们引入两条新的隐式生命周期推断规则非可逃逸值的隐式可选提升结果是一个携带与原始输入完全相同生命周期依赖的非可逃逸可选值强制解包特殊形式!与可选链特殊形式?都通过直接拷贝可选的依赖来隐式推断被包裹值若有的生命周期依赖。8.4protocol ExpressibleByNilLiteral为泛化Optional需要ExpressibleByNilLiteral协议支持非可逃逸的遵守类型。nil形式按定义需要表现得像普通的可逃逸值因此必需的初始化器需要为结果实例建立不朽immortal/static生命周期语义protocol ExpressibleByNilLiteral: ~Copyable, ~Escapable { _lifetime(immortal) // 说明性语法 init(nilLiteral: ()) }这里的_lifetime(immortal)指明该初始化器对结果生命周期不施加任何约束。既有的ExpressibleByNilLiteral遵守类型全部可逃逸而可逃逸值按定义总具有不朽生命周期因此既有一致性中的初始化器实现已经满足这一新要求——它只在新引入的~Escapable情形下才产生差异。8.5enum Optional的完整签名Optional被泛化为在不可拷贝基础上再允许非可逃逸包裹类型。enum OptionalWrapped: ~Copyable ~Escapable: ~Copyable, ~Escapable { case none case some(Wrapped) } extension Optional: Copyable where Wrapped: Copyable ~Escapable {} extension Optional: Escapable where Wrapped: Escapable ~Copyable {} extension Optional: BitwiseCopyable where Wrapped: BitwiseCopyable ~Escapable {} extension Optional: Sendable where Wrapped: ~Copyable ~Escapable Sendable {}为允许在非可逃逸可选类型上使用nil语法泛化Optional对ExpressibleByNilLiteral的一致性extension Optional: ExpressibleByNilLiteral where Wrapped: ~Copyable ~Escapable { _lifetime(immortal) // 说明性语法 init(nilLiteral: ()) }非标注的初始化器也需要泛化以支持非可逃逸情形。传入非可逃逸实体时初始化器创建的可选值与原始实体具有完全相同的生命周期依赖。此处使用假想的_lifetime(copying some)语法表示结果的生命周期依赖从some参数原样拷贝extension Optional where Wrapped: ~Copyable ~Escapable { _lifetime(copying some) // 说明性语法 init(_ some: consuming Wrapped) }语言还内建了避免调用该初始化器的构造机制隐式可选提升、显式 case 工厂它们在接收非可逃逸类型值时会隐式地把结果的生命周期依赖直接从原始输入拷贝过来。标准库自身的解包形式也需要 API 变更。take()被泛化到非可逃逸可选它把self重置为 nil并返回具有与开始时完全相同生命周期依赖的原始值。它留下的 nil 值仍受相同的生命周期约束——可变函数目前没有办法影响其self参数的生命周期依赖extension Optional where Wrapped: ~Copyable ~Escapable { _lifetime(copying self) // 说明性语法 mutating func take() - Self }unsafelyUnwrapped属性也被泛化。它暂时仍要求可拷贝性因为支持不可拷贝包裹类型需要尚未发明的新的访问器extension Optional where Wrapped: ~Escapable { _lifetime(copying self) // 说明性语法 var unsafelyUnwrapped: Wrapped { get } }如前所述nil 合并运算符??与Optional.map/.flatMap等类似高阶 API 的泛化不在本提案范围内。标准库还提供把任意可选值与nil比较的特殊支持现泛化到非可逃逸情形extension Optional where Wrapped: ~Copyable ~Escapable { static func ~( lhs: _OptionalNilComparisonType, rhs: borrowing Wrapped? ) - Bool static func ( lhs: borrowing Wrapped?, rhs: _OptionalNilComparisonType ) - Bool static func !( lhs: borrowing Wrapped?, rhs: _OptionalNilComparisonType ) - Bool static func ( lhs: _OptionalNilComparisonType, rhs: borrowing Wrapped? ) - Bool static func !( lhs: _OptionalNilComparisonType, rhs: borrowing Wrapped? ) - Bool }8.6enum Result的完整签名对Result本提案只聚焦于允许success分支包含非可逃逸值enum ResultSuccess: ~Copyable ~Escapable, Failure: Error { case success(Success) case failure(Failure) } extension Result: Copyable where Success: Copyable ~Escapable {} extension Result: Escapable where Success: Escapable ~Copyable {} extension Result: Sendable where Success: Sendable ~Copyable ~Escapable {}大多数让Result使用起来便捷的高阶函数被推迟缺乏表达生命周期依赖的手段但两个生命周期语义不复杂的函数已可泛化。mapError返回的值与原始Result实例具有相同的生命周期约束extension Result where Success: ~Copyable ~Escapable { _lifetime(copying self) // 说明性语法 consuming func mapErrorNewFailure( _ transform: (Failure) - NewFailure ) - ResultSuccess, NewFailure }get()大致等价于可选解包在非可逃逸情形下返回一个生命周期与原始Result精确匹配的值extension Result where Success: ~Copyable ~Escapable { _lifetime(copying self) // 说明性语法 consuming func get() throws(Failure) - Success }8.7enum MemoryLayout的完整签名非可逃逸类型仍有明确的内存布局布局信息与类型本身关联、与实例的生命周期约束无关因此可以泛化MemoryLayout枚举使其主体subject可以是非可逃逸类型enum MemoryLayoutT: ~Copyable ~Escapable : ~BitwiseCopyable, Copyable, Escapable {} extension MemoryLayout where T: ~Copyable ~Escapable { static var size: Int { get } static var stride: Int { get } static var alignment: Int { get } } extension MemoryLayout where T: ~Copyable ~Escapable { static func size(ofValue value: borrowing T) - Int static func stride(ofValue value: borrowing T) - Int static func alignment(ofValue value: borrowing T) - Int }8.8 生命周期管理 API 的完整签名withExtendedLifetime函数族被进一步泛化允许操作非可逃逸实体func withExtendedLifetime T: ~Copyable ~Escapable, E: Error, Result: ~Copyable ( _ x: borrowing T, _ body: () throws(E) - Result ) throws(E) - Result func withExtendedLifetime T: ~Copyable ~Escapable, E: Error, Result: ~Copyable ( _ x: borrowing T, _ body: (borrowing T) throws(E) - Result ) throws(E) - Result注意Result结果类型仍要求可逃逸。新增的无闭包变体如下func extendLifetimeT: ~Copyable ~Escapable(_ x: borrowing T)8.9 元类型相等运算符的完整签名标准库在元类型上实现的/!现泛化如下注意它们定义在可选元类型存在量词上通常依赖隐式可选提升func (t0: Any.Type?, t1: Any.Type?) - Bool { ... } func ! (t0: Any.Type?, t1: Any.Type?) - Bool { ... } func ( t0: (any (~Copyable ~Escapable).Type)?, t1: (any (~Copyable ~Escapable).Type)? ) - Bool { ... } func ! ( t0: (any (~Copyable ~Escapable).Type)?, t1: (any (~Copyable ~Escapable).Type)? ) - Bool { ... }8.10ObjectIdentifier的完整签名原初始化器接受Any.Type现泛化为也接受广义元类型存在量词extension ObjectIdentifier { init(_ x: Any.Type) } extension ObjectIdentifier { init(_ x: any (~Copyable ~Escapable).Type) }8.11ManagedBufferPointer可相等性的完整签名extension ManagedBufferPointer: Equatable where Element: ~Copyable { static func ( lhs: ManagedBufferPointer, rhs: ManagedBufferPointer ) - Bool }此类一致性泛化在新写的代码部署到泛化之前的旧平台时可能引起兼容性问题本提案预计无碍因为该泛化与先前发布的实现兼容。8.12indices的完整签名extension UnsafeBufferPointer where Element: ~Copyable { var indices: RangeInt { get } } extension UnsafeMutableBufferPointer where Element: ~Copyable { var indices: RangeInt { get } }indices比等价表达式0 .. buf.count略为便捷。8.13 缓冲区指针在Slice上的操作签名以下操作最初由 SE-0370 引入现泛化到支持不可拷贝结果元素通过把条目移出类型化可变缓冲区指针来初始化可变原始缓冲区指针的切片extension Slice where Base UnsafeMutableRawBufferPointer { func moveInitializeMemoryT: ~Copyable( as type: T.Type, fromContentsOf source: UnsafeMutableBufferPointerT ) - UnsafeMutableBufferPointerT }绑定原始缓冲区指针切片的内存extension Slice where Base UnsafeMutableRawBufferPointer { func bindMemoryT: ~Copyable( to type: T.Type ) - UnsafeMutableBufferPointerT } extension Slice where Base UnsafeRawBufferPointer { func bindMemoryT: ~Copyable( to type: T.Type ) - UnsafeBufferPointerT }在函数调用期间临时重绑定typed 或 untyped、可变或不可变的缓冲区指针切片的内存extension Slice where Base UnsafeMutableRawBufferPointer { func withMemoryReboundT: ~Copyable, E: Error, Result: ~Copyable( to type: T.Type, _ body: (UnsafeMutableBufferPointerT) throws(E) - Result ) throws(E) - Result } extension Slice where Base UnsafeRawBufferPointer { func withMemoryReboundT: ~Copyable, E: Error, Result: ~Copyable( to type: T.Type, _ body: (UnsafeBufferPointerT) throws(E) - Result ) throws(E) - Result } extension Slice { func withMemoryRebound T: ~Copyable, E: Error, Result: ~Copyable, Element ( to type: T.Type, _ body: (UnsafeBufferPointerT) throws(E) - Result ) throws(E) - Result where Base UnsafeBufferPointerElement public func withMemoryRebound T: ~Copyable, E: Error, Result: ~Copyable, Element ( to type: T.Type, _ body: (UnsafeMutableBufferPointerT) throws(E) - Result ) throws(E) - Result where Base UnsafeMutableBufferPointerElement }最后把原始缓冲区指针切片转换为类型化缓冲区指针假定其内存已绑定到正确类型extension Slice where Base UnsafeMutableRawBufferPointer { func assumingMemoryBoundT: ~Copyable( to type: T.Type ) - UnsafeMutableBufferPointerT } extension Slice where Base UnsafeRawBufferPointer { func assumingMemoryBoundT: ~Copyable( to type: T.Type ) - UnsafeBufferPointerT }所有这些都转发到 SE-0437 中已泛化的底层基础缓冲区指针操作上只是恢复缓冲区指针与其切片之间尽可能的功能对等。Slice仍要求Element可拷贝这限制了定义在它上面的其他缓冲区指针 API 的泛化。九、源码兼容性与 SE-0437 一样本提案高度依赖一个保证移除这些构造上的可逃逸假设不会破坏依赖原始可逃逸定义的现有代码。SE-0437 已探索过若干可能出问题的场景——它们可能影响依赖用自定义实现替换标准库 API 的代码。在原始未泛化定义下这类自定义重实现本可以遮蔽shadow原版但泛化之后可能不再如此进而导致模糊的函数调用。本提案主要触及 SE-0437 已改动过的 API这降低了引发新问题的可能性但它确实泛化了一些先前未改动的接口可能为这类遮蔽声明制造新的麻烦。与以往一样工程上存在缓解手段例如修订 Swift 的遮蔽规则以忽略抛出、不可拷贝性与不可逃逸性上的差异或手动修补受影响的定义让表达式检查器认为它们比任何自定义重载更不具体。十、ABI 兼容性非可逃逸类型支持总体上是编译期事务运行时影响极小甚至为零这极大简化了对既有类型的泛化。另一个简化因素是经典 Swift 代码容易意外拷贝值却很少意外逃逸参数——函数旧版本意外违反不可逃逸性的可能性低于违反不可拷贝性。实现沿用 SE-0437 的方法来保证新编译与既有二进制的向前/向后兼容包括标准库自身。预期使用本提案新特性的代码能在更早版本的 Swift 标准库上运行——前提是不可拷贝/不可逃逸类型允许 backdeploy。SE-0437 已安排 ABI 兼容符号按需导出以支持 ABI 连续性并已用强制嵌入客户端二进制的方式重实现了本提案触及的大部分入口点使本提案的改动无需额外摩擦即可 backdeploy。与 SE-0437 类似本提案假定对ExpressibleByNilLiteral协议的~Copyable/~Escapable泛化不会对既有遵守者造成 ABI 影响更进一步它还对该协议初始化器要求添加了生命周期标注这就要求此类标注也不能干扰向后/向前二进制兼容例如生命周期标注不得被 mangle 进导出的符号名。10.1 关于协议泛化的说明与 SE-0437 一致本提案基本避免泛化标准协议唯一例外是ExpressibleByNilLiteral现允许不可拷贝与不可逃逸的遵守类型。协议泛化通常不能任意 backdeploy——很可能至少需要支持限制一致性泛化的可用性。本提案沿用 SE-0437 的假设即这一潜在问题不适用于ExpressibleByNilLiteral其用例特别狭窄。若最终发现 ABI 向后兼容问题可能需要修补早期标准库中的协议一致性或把非可拷贝/非可逃逸可选的nil用法限制在足够新的运行时。以Optional对Equatable的一致性为例说明潜在问题。现有一致性限于可拷贝、可逃逸情形使用经典拷贝形式case let (l?, r?)语义上会完整拷贝两个包裹值extension Optional: Equatable where Wrapped: Equatable { public static func (lhs: Wrapped?, rhs: Wrapped?) - Bool { switch (lhs, rhs) { case let (l?, r?): return l r case (nil, nil): return true default: return false } } }当Equatable协议将来支持非可拷贝/非可逃逸遵守类型时Optional希望立即拥抱该泛化extension Optional: Equatable where Wrapped: Equatable ~Copyable ~Escapable { public static func (lhs: borrowing Wrapped?, rhs: borrowing Wrapped?) - Bool { switch (lhs, rhs) { case let (l?, r?): return l r case (nil, nil): return true default: return false } } }表面看这是简单改动但切换到borrowing参数改变了实现语义——把原始拷贝式 switch 转换为 SE-0432 引入的借用形式避免拷贝包裹值即可用于不可拷贝数据。然而旧实现假定并实际执行了可拷贝性因此Equatable一致性不能分派到早于该泛化发布的、随旧标准库一同分发的实现。缓解之道要么是追溯修补/替换先前标准库中的泛型实现要么是以不影响原始可拷贝/可逃逸一致性的方式限制泛化一致性的可用性。此问题对不可拷贝情形更紧迫因为既有实现更可能意外拷贝而非意外逃逸参数。提案的假设是ExpressibleByNilLiteral一致性通常不存在此类问题。十一、替代方案本提案的大多数改动直接源自非可逃逸类型的引入API 泛化遵循 SE-0437 确立的模式大体是机械性的。决策点主要不在于某一改动的具体形式而在于现在准备好提议哪些改动。唯一例外是全新 APIextendLifetime——它来自使用与维护withExtendedLifetime函数族的实际经验。十二、未来工作本提案主要聚焦于解决 SE-0437 愿望清单的第一项非可逃逸Optional与Result并为该提案所交付的功能增加次要的一致性改进。该提案列出的其他未来工作仍保留在议程上而非可逃逸类型又扩展出以下主题稳定的生命周期依赖语法需要定义用显式标注表达生命周期依赖的稳定语法并定义对未显式指定的函数默认应用的语义不安全机制需要一种不安全地覆盖非可逃逸实体生命周期依赖的机制未来很可能还需要允许对非可逃逸类型做不安全的位转换bit cast指针类型泛化需要允许指针类型寻址非可逃逸条目——UnsafePointer、UnsafeBufferPointer家族也许还有ManagedBuffer首要设计任务是决定指针解引用含修改应具有的生命周期语义非可逃逸容器一旦有了指针就需要允许构造非可逃逸条目的通用容器具备若干 Sequence/Collection 式能力非可拷贝/非可逃逸容器模型预计重度依赖Span类型把它作为迭代的基本单元直接访问连续存储块对非可逃逸容器而言还需把Span泛化到能捕获非可逃逸元素协议泛化希望泛化大多数既有标准库协议以允许非可逃逸遵守类型及可能的关联类型这需要为协议要求仔细添加生命周期标注并保持无缝的向前/向后兼容预计需要多个提案无法在不破坏现有代码的前提下泛化的协议可能要用全新协议替换或补充。不过非可逃逸的协议泛化通常预计比非可拷贝更顺利。十三、结语SE-0465 是非可逃逸类型从语言特性走向标准库实战的关键一步Optional与Result的非可逃逸支持让Span等类型可以安全地出现在 API 表面MemoryLayout的泛化补齐了布局查询能力extendLifetime则回应了社区真实的使用习惯。整个提案遵循先解除约束、再补语义的渐进策略把需要显式生命周期标注的复杂部分map、??、指针解引用留给未来的语言特性。对希望利用~Escapable构建高性能、内存安全 API 的开发者来说本文梳理的隐式生命周期规则、条件一致性签名与兼容性约束正是阅读 SE-0465 原文及配套提案 SE-0437、SE-0446、SE-0447、SE-0456 时最值得关注的主线。【免费下载链接】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),仅供参考
返回列表