SE-0465 深度解读:Swift 标准库为非可逃逸类型(Nonescapable Types)提供基础原语支持
2026/9/23 9:02:48 网站建设 项目流程

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-0465(Standard Library Primitives for Nonescapable Types,Swift 6.2 已实现)展开,系统讲解 Swift 标准库如何进一步拥抱~Escapable类型体系:让OptionalResult可以包裹非可逃逸值,让MemoryLayout能查询非可逃逸类型的布局信息,并新增extendLifetime()生命周期管理原语。读完本文,你将理解非可逃逸类型的隐式生命周期推断规则、标准库各 API 的泛化签名与条件约束,以及这些改动背后的源码/ABI 兼容性考量,从而能在自己的 API 设计中正确使用Span等非可逃逸类型。

一、背景与动机:从~Copyable~Escapable的第二次泛化

Swift 的"所有权控制"路线图经历了两个阶段。第一阶段由 SE-0437(Noncopyable Standard Library Primitives,Swift 6.0 实现)开启,它为OptionalResultMemoryLayoutUnsafePointer家族等标准库基础类型引入非可拷贝(~Copyable)支持;第二阶段是 SE-0446(Nonescapable Types)引入的非可逃逸(~Escapable)类型——这类值可以被局部拷贝,但不能被赋值或转移到当前上下文之外,为Span这类"借用他人存储"的安全高性能类型奠定了基础。

SE-0465 正是这两条线的交汇点:它延续 SE-0437 的工作,把泛化范围进一步扩展到非可逃逸维度,目标是:

  • 允许Optional包裹非可逃逸类型,并让Optional自身变为条件可逃逸
  • Result做同样处理,使其success分支可持有非可逃逸值;
  • 泛化MemoryLayout,允许查询非可逃逸类型的内存布局基础信息;
  • 继续泛化生命周期管理函数,并引入不带闭包参数的新函数extendLifetime()
  • 允许为不可拷贝/不可逃逸元类型生成ObjectIdentifier
  • 允许对不可拷贝/不可逃逸元类型做相等比较。

此外还修正了 SE-0437 遗留的几处与可拷贝性相关的遗漏:ManagedBufferPointerEquatable一致性、Unsafe[Mutable]BufferPointer.indices的通用化、以及缓冲区指针切片(Slice)上若干操作的泛化。相关讨论可参考 SE-0446、SE-0447(Span)与 SE-0456(标准库类型的span属性)。

需要强调的是,本提案的一切改动都以最小破坏为原则:现有代码隐式假定可拷贝与可逃逸,它必须继续像以前一样工作。

二、非可逃逸的Optional

2.1 三种构造非空非可逃逸可选值的方式

Optional需要支持包裹所有 Swift 类型,无论其是否可拷贝、是否可逃逸。这意味着Optional必须变为条件可逃逸:当它包裹非可逃逸类型时,自身也是非可逃逸的,且其生命周期约束必须与被包裹的值完全一致——把非可逃逸值包进可选值绝不能让它逃出预期上下文。

Swift 构造可选值有多种方式。给定一个非可选的Span值,以下代码演示了三种基本的非空非可逃逸可选值构造方式:

func sample(_ span: Span<Int>) { let a = Optional.some(span) // OK,显式 case 工厂 let b: Optional = span // OK,隐式可选提升 let c = Optional(span) // OK,显式初始化器调用 }

abc持有同一个 span 实例,其生命周期受制于与原始 span 相同的约束——它们可以在sample函数上下文内使用,但不能逃逸到函数之外(除非将来引入显式的生命周期依赖标注)。

2.2 三种构造空可选值的方式

同样,也必须能构造不持有任何内容的空Optional。三种基本方式全部被泛化以支持不可拷贝的包裹类型:

func sample(_ span: Span<Int>) { var d: Span<Int>? = .none // OK,显式 case 工厂 var e: Span<Int>? = nil // OK,nil 字面量表达式 var f: Span<Int>? // OK,隐式 nil 默认值 }

空的可选值技术上仍是非可逃逸的,但它天然不绑定任何上下文——空可选值生来具有"不朽"(immortal,或称 static)生命周期,即没有生命周期依赖,因此被允许在 Swift 程序的整个执行期间存在。nil 可选值可以传给任何接受非可逃逸可选的函数,也可以从任何返回非可逃逸可选的函数返回(不过 Swift 目前还没有稳定的方式来定义这类函数)。

2.3 变量重新赋值与生命周期依赖的变化

局部变量的重新赋值被允许任意改变生命周期依赖,新旧值的依赖之间不必有任何特定关系——重赋值可以自由地"收窄"或"放宽"依赖。例如下面的代码把一个可选变量初始化为不朽的 nil,随后赋给它一个具有明确生命周期约束的值,最后又把它改回不朽的 nil:

func sample(_ span: Span<Int>) { var maybe: Span<Int>? = nil // immortal maybe = span // 明确的生命周期 maybe = nil // 再次 immortal }

(把span赋给maybe不算逃逸,因为即使没有后续重赋值,局部变量也会在函数返回前被销毁。)

这种灵活性不一定适用于其他变量种类:自定义非可逃逸结构体中的存储属性、全局变量或计算属性预计会携带无法通过重赋值改变的具体生命周期依赖(例如非可逃逸类型的全局变量可能只允许持有不朽值)。不过目前提案只对局部变量进行推理。

2.4 解包机制:全部沿用既有语法

可选值如果无法判断是否含值并解包查看内容,用途就极其有限。SE-0465 保证以下常见机制全部适用于非可逃逸可选值:

  • switchif case/guard case模式匹配
// 变体 1:完整模式匹配 func count(of maybeSpan: Span<Int>?) -> Int { switch maybeSpan { case .none: return 0 case .some(let span): return span.count } } // 变体 2:带可选语法糖的模式匹配 func count(of maybeSpan: Span<Int>?) -> Int { switch maybeSpan { case nil: return 0 case let span?: return span.count } }
  • 强制解包!特殊形式,及其不安全版本、标准库的unsafelyUnwrapped属性:
func count(of maybeSpan: Span<Int>?) -> Int { if case .none = maybeSpan { return 0 } return maybeSpan!.count }
  • 可选链?特殊形式
func count(of maybeSpan: Span<Int>?) -> Int { guard let c = maybeSpan?.count else { return 0 } return c }
  • 可选绑定if let/guard let):
func count(of maybeSpan: Span<Int>?) -> 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: Span<Int>?) -> Int { if maybeSpan == nil { return 0 } // OK! return maybeSpan!.count }

2.6 暂缓的部分:高阶函数与??

这组核心功能让非可逃逸可选值可用,但还不支持更高级的 API。例如标准Optional.map(及类似高阶函数)最终希望能操作或返回非可逃逸可选类型:

func sample(_ maybeArray: Array<Int>?) { // 假设 `Array.storage` 返回非可逃逸的 `Span`: let maybeSpan = maybeArray.map { $0.storage } ... }

这些操作需要对生命周期依赖进行精确推理,因此必须等到语言提供稳定的生命周期标注语法后才能落地。同理,nil 合并运算符??也被推迟,其当前定义如下(另有返回Optional的变体):

func ?? <T: ~Copyable>( optional: consuming T?, defaultValue: @autoclosure () throws -> T ) rethrows -> T

要把它泛化到非可逃逸T,需要声明返回值的生命周期绑定于左参数生命周期与右参数(一个函数)结果生命周期的交集——目前无法表达这种约束,所以同样推迟到相应语言特性出现之后。

三、非可逃逸的Result

Result沿与Optional相同的思路泛化,允许其success分支包裹非可逃逸值。目前操作非可逃逸Result主要依赖 Swift 通用的枚举机制:switch语句、case 工厂、模式匹配、关联值绑定等。

重要的便捷 API(如Result.init(catching:)Result.map)在引入正式的生命周期依赖表达方式之前仍需要求可逃逸——但这不妨碍勇于尝试的开发者用Result值定义接口。

本提案已能泛化两个方法:get()与错误映射工具mapError

func sample<E: Error>(_ res: Result<Span<Int>, E>) -> Int { guard let span = try? res.get() else { return 42 } return 3 * span.count + 9 }

与解包Optional类似,对非可逃逸Result调用get()返回的值,其生命周期要求与原始Result实例完全一致——解包结果这一动作不能让内容逃出预期上下文。

四、查询非可逃逸类型的内存布局:MemoryLayout

本提案泛化enum MemoryLayout,使其支持获取非可逃逸类型的布局信息:

print(MemoryLayout<Span<Int>>.size) // ⟹ 16 print(MemoryLayout<Span<Int>>.stride) // ⟹ 16 print(MemoryLayout<Span<Int>>.alignment) // ⟹ 8

(返回值当然随目标架构而变化。)

这些信息的用途目前有限,因为不安全指针类型尚未泛化到支持非可逃逸 pointee(本提案不包含这部分)——但没有任何理由为此推迟布局查询能力。要让指针真正指向非可逃逸类型,需要为pointee(以及一般的指针解引用)赋予精确的生命周期语义,并且很可能还需要一种允许开发者不安全地覆盖默认生命周期语义的机制;这依赖显式生命周期标注,因而推迟到未来提案。

五、生命周期管理:泛化withExtendedLifetime与新增extendLifetime

5.1 继续泛化闭包式 API

SE-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 extendLifetime<T: ~Copyable & ~Escapable>(_ x: borrowing T)

于是上面的defer咒语可以改写为更可读的形式:

// 略有改进的现实 weak var ref = obj defer { extendLifetime(obj) } foo(ref!)

为避免破坏现有代码,本提案不废弃基于闭包的既有函数。引入新函数仍将显著减少未来 Swift 版本继续反复泛化既有函数的必要性(例如支持 async 使用,或支持非可逃逸结果)。

六、元类型比较与ObjectIdentifier

6.1 元类型相等比较

Swift 的元类型目前不遵守Equatable,但标准库仍提供实现预期相等关系的顶层==!=运算符。此前这些运算符只对CopyableEscapable类型的元类型生效,本提案放宽这一要求:

print(Atomic<Int>.self == Span<Int>.self) // ⟹ false

经典运算符支持存在元类型Any.Type;新变体还接受广义存在类型:

let t1: any (~Copyable & ~Escapable).Type = Atomic<Int>.self let t2: any (~Copyable & ~Escapable).Type = Span<Int>.self print(t1 != t2) // ⟹ true print(t1 == t1) // ⟹ true

6.2 为不可拷贝/不可逃逸类型生成对象标识

ObjectIdentifier构造主要用于生成标识类实例的Comparable/Hashable值,但它也能标识元类型:

let id1 = ObjectIdentifier(Int.self) let id2 = ObjectIdentifier(String.self) print(id1 == id2) // ⟹ false

SE-0437 没有泛化这个初始化器。SE-0465 现在允许它对不可拷贝与不可逃逸类型都生效:

import Synchronization let id3 = ObjectIdentifier(Atomic<Int>.self) // OK,不可拷贝输入类型 let id4 = ObjectIdentifier(Span<Int>.self) // OK,不可逃逸输入类型 print(id3 == id4) // ⟹ false

不可拷贝/不可逃逸类型的对象标识仍是一个普通的可拷贝、可逃逸标识——例如它可以与其他 id 比较、可以被哈希。

七、杂项修正(Odds and Ends)

7.1ManagedBufferPointer的可相等性

SE-0437 遗漏了泛化ManagedBufferPointerEquatable一致性。本提案允许即使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 工厂构造、模式匹配)的隐式生命周期规则。要泛化OptionalResult,必须先理解这些规则如何作用于带单个非可逃逸关联值的枚举类型:

  1. 用单个非可逃逸关联值构造枚举 case 时,得到的枚举值被推断为携带与原始输入完全相同的生命周期依赖;
  2. 对这种枚举 case 做模式匹配时,暴露出的非可逃逸关联值被推断为携带与原始枚举完全相同的生命周期依赖。
enum Foo<T: ~Escapable> { case a(T) case b } func test(_ array: Array<Int>) { 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 的隐式语义此处不描述,因为它与OptionalResult均无关。)

8.3Optional语法糖的隐式生命周期行为

Optional枚举附带大量直接内建在语言中的记法便捷机制。本提案为它们引入两条新的隐式生命周期推断规则:

  1. 非可逃逸值的隐式可选提升结果,是一个携带与原始输入完全相同生命周期依赖的非可逃逸可选值;
  2. 强制解包特殊形式!与可选链特殊形式?都通过直接拷贝可选的依赖来隐式推断被包裹值(若有)的生命周期依赖。

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 Optional<Wrapped: ~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语法,泛化OptionalExpressibleByNilLiteral的一致性:

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 Result<Success: ~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 mapError<NewFailure>( _ transform: (Failure) -> NewFailure ) -> Result<Success, NewFailure> }

get()大致等价于可选解包,在非可逃逸情形下返回一个生命周期与原始Result精确匹配的值:

extension Result where Success: ~Copyable & ~Escapable { @_lifetime(copying self) // 说明性语法 consuming func get() throws(Failure) -> Success }

8.7enum MemoryLayout的完整签名

非可逃逸类型仍有明确的内存布局,布局信息与类型本身关联、与实例的生命周期约束无关,因此可以泛化MemoryLayout枚举,使其主体(subject)可以是非可逃逸类型:

enum MemoryLayout<T: ~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 extendLifetime<T: ~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: Range<Int> { get } } extension UnsafeMutableBufferPointer where Element: ~Copyable { var indices: Range<Int> { get } }

indices比等价表达式0 ..< buf.count略为便捷。

8.13 缓冲区指针在Slice上的操作签名

以下操作最初由 SE-0370 引入,现泛化到支持不可拷贝结果元素:

  • 通过把条目移出类型化可变缓冲区指针来初始化可变原始缓冲区指针的切片:
extension Slice where Base == UnsafeMutableRawBufferPointer { func moveInitializeMemory<T: ~Copyable>( as type: T.Type, fromContentsOf source: UnsafeMutableBufferPointer<T> ) -> UnsafeMutableBufferPointer<T> }
  • 绑定原始缓冲区指针切片的内存:
extension Slice where Base == UnsafeMutableRawBufferPointer { func bindMemory<T: ~Copyable>( to type: T.Type ) -> UnsafeMutableBufferPointer<T> } extension Slice where Base == UnsafeRawBufferPointer { func bindMemory<T: ~Copyable>( to type: T.Type ) -> UnsafeBufferPointer<T> }
  • 在函数调用期间临时重绑定(typed 或 untyped、可变或不可变的)缓冲区指针切片的内存:
extension Slice where Base == UnsafeMutableRawBufferPointer { func withMemoryRebound<T: ~Copyable, E: Error, Result: ~Copyable>( to type: T.Type, _ body: (UnsafeMutableBufferPointer<T>) throws(E) -> Result ) throws(E) -> Result } extension Slice where Base == UnsafeRawBufferPointer { func withMemoryRebound<T: ~Copyable, E: Error, Result: ~Copyable>( to type: T.Type, _ body: (UnsafeBufferPointer<T>) throws(E) -> Result ) throws(E) -> Result } extension Slice { func withMemoryRebound< T: ~Copyable, E: Error, Result: ~Copyable, Element >( to type: T.Type, _ body: (UnsafeBufferPointer<T>) throws(E) -> Result ) throws(E) -> Result where Base == UnsafeBufferPointer<Element> public func withMemoryRebound< T: ~Copyable, E: Error, Result: ~Copyable, Element >( to type: T.Type, _ body: (UnsafeMutableBufferPointer<T>) throws(E) -> Result ) throws(E) -> Result where Base == UnsafeMutableBufferPointer<Element> }
  • 最后,把原始缓冲区指针切片转换为类型化缓冲区指针,假定其内存已绑定到正确类型:
extension Slice where Base == UnsafeMutableRawBufferPointer { func assumingMemoryBound<T: ~Copyable>( to type: T.Type ) -> UnsafeMutableBufferPointer<T> } extension Slice where Base == UnsafeRawBufferPointer { func assumingMemoryBound<T: ~Copyable>( to type: T.Type ) -> UnsafeBufferPointer<T> }

所有这些都转发到 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用法限制在足够新的运行时。

OptionalEquatable的一致性为例说明潜在问题。现有一致性限于可拷贝、可逃逸情形,使用经典拷贝形式(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 愿望清单的第一项(非可逃逸OptionalResult),并为该提案所交付的功能增加次要的一致性改进。该提案列出的其他未来工作仍保留在议程上,而非可逃逸类型又扩展出以下主题:

  1. 稳定的生命周期依赖语法:需要定义用显式标注表达生命周期依赖的稳定语法,并定义对未显式指定的函数默认应用的语义;
  2. 不安全机制:需要一种不安全地覆盖非可逃逸实体生命周期依赖的机制,未来很可能还需要允许对非可逃逸类型做不安全的位转换(bit cast);
  3. 指针类型泛化:需要允许指针类型寻址非可逃逸条目——UnsafePointerUnsafeBufferPointer家族,也许还有ManagedBuffer;首要设计任务是决定指针解引用(含修改)应具有的生命周期语义;
  4. 非可逃逸容器:一旦有了指针,就需要允许构造非可逃逸条目的通用容器,具备若干 Sequence/Collection 式能力;非可拷贝/非可逃逸容器模型预计重度依赖Span类型(把它作为迭代的基本单元,直接访问连续存储块),对非可逃逸容器而言还需把Span泛化到能捕获非可逃逸元素;
  5. 协议泛化:希望泛化大多数既有标准库协议以允许非可逃逸遵守类型(及可能的关联类型),这需要为协议要求仔细添加生命周期标注并保持无缝的向前/向后兼容,预计需要多个提案;无法在不破坏现有代码的前提下泛化的协议可能要用全新协议替换或补充。不过,非可逃逸的协议泛化通常预计比非可拷贝更顺利。

十三、结语

SE-0465 是非可逃逸类型从语言特性走向标准库实战的关键一步:OptionalResult的非可逃逸支持让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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询