ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Swift 严格内存安全模式(SE-0458)深入解读:`@unsafe`、`@safe` 属性与 `unsafe` 表达式实战指南

Swift 严格内存安全模式(SE-0458)深入解读:`@unsafe`、`@safe` 属性与 `unsafe` 表达式实战指南 Swift 严格内存安全模式SE-0458深入解读unsafe、safe属性与unsafe表达式实战指南【免费下载链接】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-0458「可选严格内存安全检查」Opt-in Strict Memory Safety Checking已实现于 Swift 6.2。它面向需要比 Swift 默认更强的内存安全保证的工程团队介绍了如何通过-strict-memory-safety编译开关、unsafe/safe属性与unsafe表达式把不安全构造的每一次使用都变成可审计、可定位、可升级为错误的警告。读完本文你将掌握严格内存安全模式的完整使用方式、其底层设计原理包括标准库标注、C/C 互操作推断规则、诊断组与 SwiftPM 集成并能结合实际源码在自有模块中落地这套机制。背景为什么需要「内存安全」与「严格内存安全」内存安全的五个维度内存安全Memory Safety是编程语言及其实现的一种属性它保证程序员错误不会在运行时演变为未定义行为。未定义行为会破坏语言的语义模型导致崩溃、数据损坏乃至不可能出现的程序状态从而带来难以复现的 bug 与安全漏洞。业界对内存安全的一种主流拆解源自 Apple 安全博客对下一代 xnu 内存安全的论述亦被 visions/memory-safety.md 采纳将其划分为五个维度生命周期安全Lifetime safety对值的所有访问都必须发生在它的生命周期内违反即所谓 use-after-free释放后使用。边界安全Bounds safety所有内存访问都落在分配的预期边界内违反即越界访问out-of-bounds。类型安全Type safety所有对值的访问都使用其初始化时的类型或兼容类型违反即类型混淆type confusion。初始化安全Initialization safety所有值在使用前都被正确初始化违反常导致信息泄露甚至升级为 use-after-free 或类型混淆。线程安全Thread safety所有值以足够同步的方式被并发访问以维持其不变量违反即数据竞争data race并可诱发其他所有内存安全问题。Swift 默认的内存安全与「刻意的不安全」从诞生之日起Swift 就对前四个维度提供了内存安全引用类型靠自动引用计数ARC保证生命周期安全值类型靠内存独占性memory exclusivity检查Array等集合的越界检查提供边界安全as?/is与enum提供类型安全「确定性初始化」definite initialization保证变量被定义前不可访问。Swift 6 的严格并发检查进一步把保证扩展到第五个维度线程安全。但 Swift 也刻意保留了一些不安全构造例如标准库的Unsafe指针类型、语言特性nonisolated(unsafe)以及与 C 这类不安全语言的互操作。对大多数开发者而言这是务实的取舍——安全且不碍事。然而有些项目希望获得比默认更强的保证它们想更密切地关注代码中对不安全构造的使用并在存在安全替代品时阻止「随手」使用不安全构造。这正是 SE-0458 的出发点引入可选opt-in的严格内存安全检查识别 Swift 代码中使用了不安全语言构造与 API 的位置。任何写在这个严格安全子集内的代码同时也都是「正常」的 Swift 代码可以与现有 Swift 代码互操作。提案总体方案四个组成构件SE-0458 提出的解决方案包含四个相互配合的部分详见 proposals/0458-strict-memory-safety.md编译器开关-strict-memory-safety开启后模块内所有不安全构造的使用都会产生警告。全部警告归属于StrictMemorySafety诊断组从而可以按 SE-0443 精确警告控制 flags 精确控制其行为。开启时还会设置StrictMemorySafetyfeature代码可用#if hasFeature(StrictMemorySafety)检测当前是否处于该编译模式。属性unsafe标记某个声明「使用它是不安全的」。这类声明允许在其签名中使用不安全构造。属性safe标记某个「签名中含有不安全构造、但实际使用是安全的」声明。例如Array.withUnsafeBufferPointer的签名里含有不安全类型self的类型本身安全但它把不安全缓冲区指针交给闭包可该方法自身负责了安全边界因此标注safe真正的不安全则发生在闭包内部使用该指针时。unsafe表达式与try、await类似在表达式层面对不安全构造的使用进行标记。此外标准库会同步为各类不安全声明加上标注这是整个机制能运转的前提。一个最小示例unsafe表达式的使用UnsafeBufferPointer连同UnsafePointer、UnsafeRawPointer等将在标准库中被标记为unsafeunsafe public struct UnsafeBufferPointerElement { ... }这意味着该类型的使用不是内存安全的。任何把UnsafeBufferPointer作为其类型一部分的声明都会被隐式视为unsafe// 注意由于使用了不安全类型 UnsafePointer隐式为 unsafe func sumIntBuffer(_ address: UnsafePointerInt?, _ count: Int) - Int { ... }开启严格安全检查的调用方使用该函数时会看到警告。例如extension ArrayInt { func sum() - Int { withUnsafeBufferPointer { buffer in // warning: use of unsafe function sumIntBuffer and unsafe property baseAddress sumIntBuffer(buffer.baseAddress, buffer.count, 0) } } }对sumIntBuffer的调用与对UnsafeBufferPointer.baseAddress属性的访问都涉及不安全代码因此都会产生警告。值得注意的是即使声明没有显式标注unsafe只要签名中带有不安全类型就会隐式成立——这让依赖库即使自身未开启严格安全检查也能暴露其不安全面。消除警告的方式是把涉及不安全代码的表达式用unsafe标记正如用try标记可抛表达式、用async标记异步表达式extension ArrayInt { func sum() - Int { withUnsafeBufferPointer { buffer in unsafe sumIntBuffer(buffer.baseAddress, buffer.count, 0) } } }这里的unsafe关键字表示该表达式内存在不安全代码。和try/await一样它可以覆盖表达式内的多处不安全来源调用sumIntBuffer是不安全的使用buffer与buffer.baseAddress也是不安全的但一个unsafe即可全部覆盖。编写不安全 API 的作者有责任文档化其使用条件使用方则有责任在unsafe表达式内确保这些条件成立。与try不同unsafe不会向外传播sum函数体内有不安全代码但不要求sum被标记为unsafe调用withUnsafeBufferPointer也不因其闭包不安全而需要unsafe标记。程序员可以主动把sum标为不安全但设计假设是只要签名不含不安全类型用unsafe封装即可保证行为安全。更完整的例子UnsafeMutableBufferPointer.swapAtUnsafeMutableBufferPointer.swapAt交换缓冲区中两个下标处的值。在严格安全模式下它的实现如下摘自提案原文extension UnsafeMutableBufferPointer { /*隐式 unsafe*/ public func swapAt(_ i: Index, _ j: Index) { guard i ! j else { return } precondition(i 0 j 0) precondition(i endIndex j endIndex) let pi unsafe (baseAddress! i) let pj unsafe (baseAddress! j) let tmp unsafe pi.move() unsafe pi.moveInitialize(from: pj, count: 1) unsafe pj.initialize(to: tmp) } }swapAt的实现混合了安全与不安全代码。标有unsafe的代码是 Swift 无法验证内存安全性的操作对baseAddress做指针运算Swift 无法推理底层指针的生命周期也无法确认运算结果仍落在分配的边界内实际移动与初始化元素这些元素必须已经被初始化。代码自身用precondition保证下标不越界但还有一些无法用前置条件检查的安全属性指针关联的内存已被正确初始化、其生命周期覆盖整个调用、且未被其他代码并发使用——这些必须由swapAt的调用方来保证。因此swapAt被视为unsafe由于它属于不安全类型隐式成立作者也可显式标注。详细设计上不安全来源全清单严格安全模式要识别所有引入内存不安全的位置。提案从语言构造、标准库 API、编译器 flags、C/C 互操作四个层面做了系统枚举。不安全的语言构造始终不安全以下语言构造在任何情况下都被视为不安全unowned(unsafe)存储引用但不维护引用计数。安全版本unowned会做动态检查确保对象释放后不被访问unsafe变体禁用该检查。对unowned(unsafe)实体的使用不是内存安全的。unsafeAddressor、unsafeMutableAddressor这类访问器出售不安全指针声明它本身就不安全get/set等访问器可提供安全替代。它们被视为所属属性/下标的签名一部分使属性隐式unsafe除非显式标safe。exclusivity(unchecked)移除对某个变量的动态独占性检查运行时独占性违规将无法被发现从而造成内存安全违规。对exclusivity(unchecked)实体的使用不安全。严格并发开启时Swift 6 语言模式下才不安全nonisolated(unsafe)允许属性在并发代码中被访问而不保证访问安全。对其实体的使用不安全。preconcurrency导入抑制针对特定导入模块的数据竞争安全诊断可能引入线程安全问题。在严格安全模式下preconcurrency导入需要被标注为unsafe。unsafe属性与签名推断规则unsafe可作用于任意声明表示「使用该声明可能破坏内存安全」。例如unsafe public struct DataWrapper { var buffer: UnsafeBufferPointerUInt8 } unsafe func writeToRegisterAt(integerAddress: Int, value: Int32) { ... }对DataWrapper与writeToRegisterAt在可执行代码中的使用都会被判定为内存不安全。签名中出现不安全类型 ⇒ 声明隐式unsafe。声明签名是声明向客户端呈现的接口包括函数的参数与返回类型、属性的类型、泛型参数与约束。例如DataWrapper上的方法extension DataWrapper { public func checksum() - Int32 { crc32(0, buffer.baseAddress, buffer.count) } }由于隐式self参数的类型是unsafe的checksum()隐式成为unsafe。一个关键例外是默认参数默认参数属于函数实现而非签名。因此下面的函数签名中没有任何不安全类型即使value的默认参数涉及不安全代码——这段不安全代码本质上是函数体的一部分遵循unsafe表达式的规则func hasDefault(value: Int unsafe getIntegerUnsafely()) { ... }不安全的协议实现conformances一个类型可能以引入不安全性的方式实现某个协议——例如UnsafeBufferPointer对Collection的遵循无法做到安全因为它不能向Collection的客户端提供生命周期、边界或初始化安全保证。这类遵循应显式标注unsafeextension UnsafeBufferPointer: unsafe Collection { ... }不安全遵循与不安全类型类似它出现在声明签名中会使该声明隐式unsafe。例如对UnsafeBufferPointer使用firstIndex(where:)这类集合算法会因上述unsafe遵循而被判定为不安全。标准库中的不安全 API 与不安全遵循严格安全模式识别的相当一部分不安全代码来自标准库内部不安全声明的标注及其向其他类型的传播。提案明确将被标记unsafe的标准库类型与函数包括API不安全维度Unsafe(Mutable)(Raw)(Buffer)Pointer、OpaquePointer、CVaListPointer既不提供生命周期安全也不提供边界安全长期来看代码应迁移到(Raw)Span等安全替代(Closed)Range.init(uncheckedBounds:)可构造不满足不变量下界不超过上界的范围破坏Array.subscript等依赖的边界检查Span.subscript(unchecked:)未经检查的下标使用可引入边界安全问题Unmanaged显式禁用引用计数的包装器可能引入生命周期安全问题unsafeBitCast无法确认安全的类型转换可能引入类型安全问题unsafeDowncastas!的未检查形式可能引入类型安全问题Optional.unsafelyUnwrapped对可选值后置!的未检查形式nil被解释为类型化值时可引发类型/初始化/生命周期问题UnsafeContinuation、withUnsafe(Throwing)ContinuationwithChecked(Throwing)Continuation的不安全形式不验证 continuation 恰好被调用一次withUnsafeCurrentTask、UnsafeCurrentTaskUnsafeCurrentTask不提供生命周期安全只能在传给withUnsafeCurrentTask的闭包内使用UnownedSerialExecutor刻意不做生命周期安全主要用途是Actor协议的unownedExecutor属性规则是这些 API 全部标unsafe签名涉及不安全类型的标准库 API 中使用安全者标safe需要使用者自行维护某种安全性的标unsafe签名不含不安全类型、但实现中使用不安全构造的 API默认视为安全。标准库还存在不安全遵循Unsafe(Mutable)(Raw)BufferPointer对Sequence及Collection协议层次的遵循全部unsafeUnsafe(Mutable)(Raw)Pointer对Strideable的遵循unsafe。不安全的编译器 flags以下编译器 flags 会刻意禁用部分安全检查与严格内存安全同时使用时编译器将给出诊断-Ounchecked禁用标准库中的部分检查例如数组访问的越界检查-enforce-exclusivityunchecked与-enforce-exclusivitynone禁用内存安全所需的独占性检查-strict-concurrency取非 complete 的值内存安全模型要求严格并发以消除线程安全问题-disable-access-control允许破坏类型不变量例如Range下界不超过上界的约束引发内存安全问题。C/C 互操作指针意味着不安全C 语言家族没有与严格安全模式对应的机制其默认沿所有维度都可能不安全。C() 指针通常被导入为某种Unsafe*Pointer因此含指针的 C API 会在 Swift 中隐式视为unsafe。例如func strstr( _ haystack: UnsafePointerCChar?, _ needle: UnsafePointerCChar? ) - UnsafeMutablePointerCChar?而签名中不含指针的 C 函数如int getchar(void);导入为func getchar() - CInt隐式视为安全。对于 C/C 用户自定义类型struct、enum、union、class当它们的非静态数据成员包含任何 C 指针或显式标记为不安全的 C 类型时该类型会被推断为unsafe。例如纯数值的Point { double x, y; }是安全的而含指针成员的ListNode { void *element; struct ListNode *next; }隐式unsafe。C 的enum永远不会被推断为unsafe因为除底层整型总是安全类型外它不携带任何值。详细设计下如何在源码中「承认」不安全上面所有特性在 Swift 中都可用无论是否开启严格检查。开启后每一种不安全构造的使用都必须以下述方式之一在源码中得到承认形成可审计的源码级标记。unsafe表达式任何执行了不安全构造的可执行代码若不在unsafe表达式内编译器都会产生诊断。以DataWrapper为例在checksum实现中使用不安全类型属性buffer时warning: expression uses unsafe constructs but is not marked with unsafe用unsafe表达式即可消除extension DataWrapper { public func checksum() - Int32 { unsafe crc32(0, buffer.baseAddress, buffer.count) } }unsafe表达式与try/await相似它承认子表达式内使用了不安全构造但不改变类型。区别在于try/await会要求外层上下文处理抛错/处于异步环境而unsafe不对外层块附加任何要求——它纯粹是「标记存在不安全代码、消除诊断」的指示符。safe属性签名不安全但使用安全safe用于签名涉及不安全类型、但使用却是安全的声明。既然UnsafeBufferPointer标了unsafe所有涉及不安全缓冲指针的操作都会隐式被视为unsafesafe则说明某些操作实际上安全——例如只涉及下标或计数的操作并不触碰内存本身extension UnsafeBufferPointer { safe public let count: Int safe public var startIndex: Int { 0 } safe public var endIndex: Int { count } }对Array而言withUnsafeBufferPointer本身也涉及它交给闭包的不安全类型但数组承担了所提供的缓冲区指针的内存安全责任元素必然已初始化、边界正确、提供期间无其他人访问因此可以标safe不安全发生在闭包对UnsafeBufferPointer的使用中extension Array { safe func withUnsafeBufferPointerR, E( _ body: (UnsafeBufferPointerElement) throws(E) - R ) throws(E) - R }配合 C 库函数的典型使用extension ArrayInt { func sum() - Int { withUnsafeBufferPointer { buffer in unsafe c_library_sum_function(buffer.baseAddress, buffer.count, 0) } } }safe标注会为其直接参数含self中的不安全类型变量承担责任若这样的变量被用于访问safe属性/下标或作为safe函数的实参则不会诊断为不安全extension ArrayInt { func sum() - Int { withUnsafeBufferPointer { buffer in let count buffer.count // count 是 safe即使 buffer 是不安全类型也不诊断 let address buffer.baseAddress // warning: buffer 与 baseAddress 都不安全 c_library_sum_function(address, count, 0) // warning: c_library_sum_function 与 address 都不安全 } } }含不安全存储的类型包装不安全类型的类型通常会把不安全行为封装起来、对外提供安全接口——但这需要刻意设计与实现例如添加特定前置条件。开启严格检查后存储包含不安全类型或不安全遵循的类型会被诊断为涉及不安全代码public struct DataWrapper { var buffer: UnsafeBufferPointerUInt8 }会得到warning: type DataWrapper that includes unsafe storage must be explicitly marked unsafe or safe正如警告所示可通过把类型标为safe或unsafe消除诊断。DataWrapper看不出为存储提供安全性应标unsafe。反过来可以把不安全类型包成安全类型// 需要 safe 来消除关于 buffer 属性使用不安全类型的诊断 safe struct ImmortalBufferWrapperElement : Collection { let buffer: UnsafeBufferPointerElement unsafe init(_ withImmortalBuffer: UnsafeBufferPointerElement) { self.buffer unsafe buffer } subscript(index: Index) - Element { precondition(index 0 index buffer.count) return unsafe buffer[index] } /* 还需Index、startIndex、endIndex、index(after:) */ }标准库大量是由「不安全代码之上的安全抽象」构成的提案预期用户代码也会如此。判定「类型有不安全存储」的规则任何存储实例属性actor/class/struct或关联值enum的 case的类型涉及不安全类型或不安全遵循任何存储实例属性使用了不安全语言特性如unowned(unsafe)。不安全的 witness协议要求的实现类型遵循协议时必须满足所有要求其中某个声明witness具体实现对应的协议要求。若 witness 不安全而对应要求是安全的编译器会产生警告protocol P { func f() } struct ConformsToP { } extension ConformsToP: P { unsafe func f() { } // warning: unsafe instance method f() cannot satisfy safe requirement }把遵循本身标为unsafe即可消除extension ConformsToP: unsafe P { unsafe func f() { } // 可以这是不安全遵循 }不安全的覆写overrides用不安全方法覆写安全方法可能引入不安全严格安全模式下会产生诊断class Super { func f() { } } class Sub: Super { unsafe override func f() { ... } // warning: override of safe instance method with unsafe instance method }消除方式是把Sub类整体标为unsafeunsafe class Sub: Super { override func f() { ... } // 不再警告 }unsafe放在类层面是因为对Sub的任何使用都可能引入不安全行为且一旦Sub被当作Super实例使用不安全指示就会丢失。for..in循环与unsafe迭代for..in本质上是Sequence/IteratorProtocol的语法糖。若涉及的遵循是unsafe循环会产生警告let someUnsafeBuffer: UnsafeBufferPointerElement unsafe ... for x in someBuffer { // warning: use of unsafe conformance of UnsafeBufferPointer to Sequence // 且 someBuffer 是不安全类型 UnsafeBufferPointer // ... }效仿 SE-0298 在循环中引入 effect 的先例承认迭代中不安全行为的unsafe关键字跟在for之后let someUnsafeBuffer: UnsafeBufferPointerElement unsafe ... for unsafe x in someBuffer { // 仍警告 someBuffer 是不安全类型 UnsafeBufferPointer // ... }产生序列的表达式本身对someBuffer的引用也是不安全的还需单独承认let someUnsafeBuffer: UnsafeBufferPointerElement unsafe ... for unsafe x in unsafe someBuffer { // ... }这种重复unsafe与其它 effect 一致若getAsyncSequence()返回迭代可抛错的AsyncSequence就会出现两个try与两个awaitfor try await x in try await getAsyncSequence() { ... }开启严格安全模式与警告升级严格内存安全模式通过新编译器开关-strict-memory-safety启用。该模式产生的所有内存安全诊断都是警告归属于StrictMemorySafety组可能组织为子组因此可以借助 SE-0443 引入的诊断组控制 flags 选择升级为错误或维持警告。例如开启模式并把内存安全问题变成错误swiftc -strict-memory-safety -Werror StrictMemorySafetySE-0443 的机制值得在这里展开编译器为诊断引入「诊断组」diagnostic group这一稳定标识符并提供-Werror group把该组警告升级为错误与-Wwarning group保持为警告两个新选项配合既有的-warnings-as-errors/-no-warnings-as-errors按「最后一个生效」的规则统一处理-print-diagnostic-groups可在警告文本后打印所属最窄组名形如[#DeprecatedDeclaration]。正是这套设施让StrictMemorySafety组的警告既能保持温和提醒也能在 CI 等场景下被一键升级为硬性错误。SwiftPM 集成按模块/包开启Swift 包清单需要一种按模块、按包开启严格内存安全模式的方式。提案扩展清单中的SwiftSetting类型新增选项static func strictMemorySafety( _ condition: BuildSettingCondition? nil ) - SwiftSettingcondition参数允许按平台或编译配置等条件来决定是否启用从而实现精细的渐进式落地。源码兼容性、ABI 兼容性与增量采用源码兼容性unsafe关键字将按 SE-0296 引入await、SE-0366 引入consume的先例作为上下文关键字引入。这意味着unsafe仍可用作标识符但存在一个小的破坏面——把unsafe当作函数并以尾随闭包调用func unsafe(_ body: () - Void) { } unsafe { // 目前调用 unsafe(_:)之后将成为 unsafe 表达式 }该破坏影响面预计足够小。若不可接受可将unsafe表达式的解析限制在已启用严格安全检查的代码中。除此之外对未开启该模式的模块此机制完全不影响源码兼容性开启时的影响也仅限于警告且可用前述诊断组 flags 在-warnings-as-errors下维持警告。ABI 兼容性所提出的属性、unsafe表达式与检查模型对 ABI 没有任何影响。增量采用严格安全检查强制的是 Swift 的一个子集子集内代码必须是合法 Swift 且能与未启用检查的代码互操作。对比两个相近努力严格并发检查Swift 6 语言模式核心要求类型系统大改涉及Sendable传播与主 actor 隔离理解等全局属性无法局部推理或局部修复而严格安全检查对类型系统几乎没有影响不安全性可用unsafe表达式封装或由未启用检查的模块直接忽略。Embedded Swift参见 proposals/0337-support-incremental-migration-to-concurrency-checking.md是无运行时子集严格安全子集与它正交可组合使用例如确保纯 Swift 固件既无运行时又具备最佳内存安全保证。启用严格安全检查的模块可以通过在恰当位置施加unsafe属性与unsafe表达式来处理所有诊断无需改动其他代码该标注过程可通过 Fix-It 自动化即「一键开启模式并静默全部诊断」随后由程序员逐一审计那些用unsafe封装不安全行为的位置确认其确实安全。需要强调的是严格安全检查本身不会让代码更安全它的价值在于识别不安全构造、鼓励采用安全替代、使不安全行为的审计更便捷。unsafe标注对未开启严格检查的客户端毫无影响已开启的客户端会开始诊断新标注 API 的使用但这些都是拥有独立诊断组的警告不会阻断构建——因此模块可按自己的节奏或不采用严格安全检查客户端永远不会被「卡住」被迫做大规模改动。未来方向SerialExecutor与Actor协议SerialExecutor协议对严格安全模式构成独特挑战由于unownedExecutor要求的存在完全用安全代码实现该协议是不可能的protocol SerialExecutor: Executor { // ... unsafe var unownedExecutor: UnownedSerialExecutor { get } }要使SerialExecutor可安全实现需要为其扩展unownedExecutor的安全形式这很可能要求一种 非可逃逸non-escapable的UnownedSerialExecutor在零开销前提下提供生命周期安全Actor协议有同样的要求。Swift 实现需改用新要求来调度 actor 上的工作以消除对不安全构造的隐式使用。此外SerialExecutor还承担「串行执行」的语义约束——数据竞争安全模型进而内存安全模型依赖它正确实现该语义一个并发执行两个 job 的遵循类型就会制造内存安全违规。提案讨论了三种处理思路把协议整体标unsafe把部分要求如unownedExecutor的替代品标unsafe或要求遵循时给出某种证明如safe(unchecked)。前两者最直接但 actor 对SerialExecutor的隐式使用会事实性地让每个 actor 都变成unsafe把承认责任推给客户端而非真正承担实现责任的遵循类型第三种方案更优因为它在源码中提供了一个可审计的位置来断言内存安全。若未来出现更多此类协议可考虑提供unsafe(conforms)这种仅对遵循类型不安全的属性变体。unsafe枚举 case 的模式匹配当枚举 case 显式标unsafe但关联数据并不不安全时提案暂无手段在模式匹配该 case 时抑制诊断enum WeirdAddress { unsafe case rawOffsetIntoGlobalArray(Int) } func example(_ address: WeirdAddress) { if case .rawOffsetIntoGlobalArray(let offset) weirdAddress { // 引用 unsafe case 无法抑制 } }可能的出路为这种unsafe case使用抑制诊断构造时仍诊断拒绝在无不安全类型时给 case 标unsafe或扩展模式语法引入unsafe模式if case unsafe .rawOffsetIntoGlobalArray(let offset) weirdAddress { ... }宏展开中的不安全代码宏可展开为任意代码。若展开结果包含未被safe/unsafe/unsafe表达式覆盖的不安全构造严格安全检查会在展开处给出诊断而宏的使用方无法在不修改宏实现的前提下在展开内部抑制诊断。两种可能的方案让unsafe表达式覆盖整个宏展开还需为属性等非表达式位置设计拼写或引入一种「在一段代码块内抑制某类警告」的通用语法来包裹宏展开。两者都会为了抑制的便利而牺牲严格安全模式的部分收益。备选方案回顾设计权衡提案在多个关键设计点上做了取舍理解这些取舍有助于把握机制本质禁止不安全遵循与覆写不禁止。虽然完全禁止可迫使代码引入更多抽象例如用带越界检查的包装类型替代parse(unsafeBufferPointer)这类调用但禁止并不能从根本上改变安全模型反而可能只是制造模板化样板代码。Swift 已有unchecked Sendable、nonisolated(unsafe)、unowned(unsafe)、preconcurrency等「可在局部关闭检查但影响广泛」的构造先例。unsafe蕴含整个函数体不安全不采用。那会减少标注负担但让人难以分辨实现中到底哪些部分不安全Rust 的unsafe fn正是此行为Rust RFC #2585 主张移除它其动机同样适用于 Swift还会混淆「暴露不安全接口」与「实现不安全」两个概念。把「封装」显式化safe(unchecked)或unsafe!不采用。签名无任何不安全类型本身已是「提供安全接口」的强信号再要求额外仪式收益甚微。unsafe块类似 C#/Rust提案选择unsafe表达式而非unsafe { ... }块。块粒度更粗、对不安全密集代码噪音更小但会隐藏块内真正不安全的代码、加大审计难度unsafe表达式在语言层面强制了「块尽量窄」的最佳实践。默认开启严格安全明确否决Unsafe指针类型仍是当前操作连续内存的唯一途径安全替代如Span在生态中普及尚需时日C() 互操作是 Swift 的重要特性而多数 C() API 不太可能采用这些安全属性Swift 当前的默认内存安全对绝大多数用户已足够默认升级的收益难抵扰动。这也正是该特性作为可选模式而非 upcoming 语言特性推出的原因upcoming 机制见 proposals/0362-piecemeal-future-features.md。重载以渐进引入安全 API探讨了利用类型重载把bytes: UnsafeRawBufferPointer平稳升级为bytes: RawSpan的可能性例如safe(unsafeDisfavored)属性配合严格模式下unsafe声明不被偏好但指出它会破坏源码兼容故事——类型推断在开启模式后行为改变可能引入错误而非警告风险较高。unsafe的可选message参数省略。注释能提供同样信息且已有成熟工具链向程序员呈现注释没必要为属性消息另建通道。与内存安全愿景的关系SE-0458 是 visions/memory-safety.md 愿景Optional Strict Memory Safety for Swift已获语言指导组背书落地中的关键一步。愿景文档提出了更宏大的图景编译器选项把任何不安全代码的使用变成错误SE-0458 以警告可升级方式实现unsafe属性标注以及通过Span等安全抽象提升严格安全子集的表达能力——SE-0447 的Span正是为连续内存访问提供生命周期与边界双安全其下标总是做越界检查、不逃逸其有效作用域的「货币类型」与Unsafe指针家族形成「安全替代 vs 不安全原语」的对照。愿景还设想通过 C 头文件注解如__counted_by、noescape、lifetimebound把 C() API 投影为不含不安全指针的安全 Swift API以及为审计工具提供能力来报告未启用严格安全的模块与项目中所有使用 opt-out 机制的位置。SE-0458 的实现为这套愿景提供了语言机制基础。快速上手清单启用模式swiftc -strict-memory-safety或在 Package.swift 中用.strictMemorySafety()按模块开启。把警告升级为错误可选swiftc -strict-memory-safety -Werror StrictMemorySafety。用#if hasFeature(StrictMemorySafety)检测当前编译模式按需切换代码路径。处理诊断安全替代优先如Span替代Unsafe(Mutable)BufferPointer确实需要不安全构造时用unsafe表达式覆盖表达式、用unsafe标注暴露不安全接口的声明、用safe标注「签名含不安全类型但使用安全」的声明如withUnsafeBufferPointer、用unsafe遵循/类型标注处理协议实现与不安全存储。审计把所有unsafe出现之处视为待审查清单逐一验证前置条件生命周期、初始化状态、边界、并发独占成立。【免费下载链接】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),仅供参考
返回列表