Skip to content

Commit 1f5a3ec

Browse files
committed
Add a section on @unsafe implying unsafe throughout a function body
Plus some minor typo corrections and clarifications.
1 parent 6cb9de2 commit 1f5a3ec

File tree

1 file changed

+46
-6
lines changed

1 file changed

+46
-6
lines changed

proposals/nnnn-strict-memory-safety.md

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ While there are a number of potential definitions for memory safety, the one pro
3434

3535
Since its inception, Swift has provided memory safety for the first four dimensions. Lifetime safety is provided for reference types by automatic reference counting and for value types via [memory exclusivity](https://www.swift.org/blog/swift-5-exclusivity/); bounds safety is provided by bounds-checking on `Array` and other collections; type safety is provided by safe features for casting (`as?` , `is` ) and `enum` s; and initialization safety is provided by “definite initialization”, which doesn’t allow a variable to be accessed until it has been defined. Swift 6’s strict concurrency checking extends Swift’s memory safety guarantees to the last dimension.
3636

37-
Providing memory safety does not imply the absence of run-time failures. Good language design often means defining away runtime failures in the type system. However, memory safely requires only that an error in the program cannot be escalated into a violation of one of the safety properties. For example, having reference types be non-nullable by default defines away most problems with NULL pointers. With explicit optional types, the force-unwrap operator (postfix `!` ) meets the definition of memory safety by trapping at runtime if the unwrapped optional is `nil` . The standard library also provides the [`unsafelyUnwrapped` property](https://developer.apple.com/documentation/swift/optional/unsafelyunwrapped) that does not check for `nil` in release builds: this does not meet the definition of memory safety because it admits violations of initialization and lifetime safety that could be exploited.
37+
Providing memory safety does not imply the absence of run-time failures. Good language design often means defining away runtime failures in the type system. However, memory safety requires only that an error in the program cannot be escalated into a violation of one of the safety properties. For example, having reference types be non-nullable by default defines away most problems with NULL pointers. With explicit optional types, the force-unwrap operator (postfix `!` ) meets the definition of memory safety by trapping at runtime if the unwrapped optional is `nil` . The standard library also provides the [`unsafelyUnwrapped` property](https://developer.apple.com/documentation/swift/optional/unsafelyunwrapped) that does not check for `nil` in release builds: this does not meet the definition of memory safety because it admits violations of initialization and lifetime safety that could be exploited.
3838

3939
## Proposed solution
4040

@@ -106,7 +106,7 @@ extension Array<Int> {
106106

107107
The `unsafe` keyword here indicates the presence of unsafe code within that expression. As with `try` and `await`, it can cover multiple sources of unsafety within that expression: the call to `c_library_sum_function` is unsafe, as is the use of `buffer` and `buffer.baseAddress`, yet they are all covered by one `unsafe`.
108108

109-
Note that we do *not* require that the `sum` function be marked `@unsafe` just because it has unsafe code in it. The programmer may choose to indicate that `sum` is unsafe, but the assumption is that unsafe behavior is properly encapsulated when using `unsafe`.
109+
Note that we do *not* require that the `sum` function be marked `@unsafe` just because it has unsafe code in it. The programmer may choose to indicate that `sum` is unsafe, but the assumption is that unsafe behavior is properly encapsulated when using `unsafe`. Additionally, the `@unsafe` attribute is available for all Swift code, even if it doesn't itself enable the strict safety checking described in this proposal.
110110

111111
### Incremental adoption
112112

@@ -197,12 +197,12 @@ The following language constructs are always considered to be unsafe:
197197

198198
* `unowned(unsafe)`: Used to store a reference without maintaining its reference count. The safe counterpart, `unowned`, uses dynamic checking to ensure that the reference isn't accessed after the corresponding object has been released. The `unsafe` variant disables that dynamic checking. Uses of `unowned(unsafe)` entities are not memory-safe.
199199
* `unsafeAddressor`, `unsafeMutableAddressor`: These accessors vend an unsafe pointer, and are therefore unsafe to declare. Other accessors (e.g., `get` and `set`) can provide safe alternatives.
200-
* `@exclusivity(unchecked)`: Used to remove dynamic exclusivity checks from a particular variable, which can mean that dynamic exclusivity violations go undetected at run time, causing a memory safety violation.
200+
* `@exclusivity(unchecked)`: Used to remove dynamic exclusivity checks from a particular variable, which can mean that dynamic exclusivity violations go undetected at run time, causing a memory safety violation. Uses of `@exclusivity(unchecked)` entities are not memory-safe.
201201

202202
The following language constructs are considered to be unsafe when strict concurrency checking is enabled (i.e., in the Swift 6 language mode):
203203

204204
* `nonisolated(unsafe)`: Allows a property to be accessed from concurrent code without ensuring that such accesses are done so safely. Uses of `nonisolated(unsafe)` entities are not memory-safe.
205-
* `@preconcurrency` imports: Suppresses diagnostics related to data race safety when they relate to specific imported modules, which can introduce thread safety issues.
205+
* `@preconcurrency` imports: Suppresses diagnostics related to data race safety when they relate to specific imported modules, which can introduce thread safety issues. The `@preconcurrency` import will need to be annotated with `@unsafe` in the strict dsafety mode.
206206

207207
### Unsafe standard library APIs
208208

@@ -317,7 +317,7 @@ The `unsafe` keyword in this proposal will be introduced as a contextual keyword
317317
func unsafe(_ body: () -> Void) { }
318318

319319
unsafe {
320-
// currently calls 'unsafe(_:)', but will become and unsafe expression
320+
// currently calls 'unsafe(_:)', but will become an unsafe expression
321321
}
322322
```
323323

@@ -408,6 +408,46 @@ There are downsides to this approach. It partially undermines the source compati
408408

409409
## Alternatives considered
410410

411+
### `@unsafe` implying `unsafe` throughout a function body
412+
413+
A function marked `@unsafe` is unsafe to use, so any clients that have enabled strict safety checking will need to put uses of the function into an `unsafe` expression. The implementation of that function is likely to use unsafe code (possibly a lot of it), which could result in a large number of annotations:
414+
415+
```swift
416+
extension UnsafeMutableBufferPointer {
417+
@unsafe public func swapAt(_ i: Int, _ j: Int) {
418+
guard i != j else { return }
419+
precondition(i >= 0 && j >= 0)
420+
precondition(unsafe i < endIndex && j < endIndex)
421+
@unsafe let pi = unsafe (_position! + i)
422+
@unsafe let pj = unsafe (_position! + j)
423+
@unsafe let tmp = unsafe pi.move()
424+
unsafe pi.moveInitialize(from: pj, count: 1)
425+
unsafe pj.initialize(to: tmp)
426+
}
427+
}
428+
```
429+
430+
Now, it is very likely that an `@unsafe` function is going to make use of other unsafe constructs, so we could choose to make `@unsafe` on a function acknowledge all uses of unsafe code within its definition. For example, this would mean that marking `swapAt` with `@unsafe` means that one need not have any `unsafe` expressions in its body:
431+
432+
```swift
433+
extension UnsafeMutableBufferPointer {
434+
@unsafe public func swapAt(_ i: Int, _ j: Int) {
435+
guard i != j else { return }
436+
precondition(i >= 0 && j >= 0)
437+
precondition(i < endIndex && j < endIndex)
438+
let pi = (_position! + i)
439+
let pj = (_position! + j)
440+
let tmp = pi.move()
441+
pi.moveInitialize(from: pj, count: 1)
442+
pj.initialize(to: tmp)
443+
}
444+
}
445+
```
446+
447+
This approach reduces the annotation burden in unsafe code, but makes it much harder to tell exactly what aspects of the implementation are unsafe. Indeed, even unsafe functions should still strive to minimize the use of unsafe constructs, and benefit from having the actual unsafe behavior marked in the source. It also conflates the notion of "exposes an unsafe interface" from "has an unsafe implementation".
448+
449+
Rust's `unsafe` functions have this behavior, where an `unsafe fn` in Rust implies an `unsafe { ... }` block around the entire function body. [Rust RFC #2585](https://rust-lang.github.io/rfcs/2585-unsafe-block-in-unsafe-fn.html) argues for Rust to remove this behavior; the motivation there generally applies to Swift as well.
450+
411451
### `@safe(unchecked)` attribute to allow unsafe code
412452

413453
Early iterations of this proposal introduced a `@safe(unchecked)` attribute as an alternative to `unsafe` expressions. The `@safe(unchecked)` attribute would be placed on a function to suppress diagnostics about use of unsafe constructs within its definition. For our `sum` example, this means one would write:
@@ -461,4 +501,4 @@ We could introduce an optional `message` argument to the `@unsafe` attribute, wh
461501

462502
## Acknowledgments
463503

464-
This proposal has been greatly improved by the feedback from Félix Cloutier, Gábor Horváth, Frederick Kellison-Linn, Karl Wagner, and Xiaodi Wu.
504+
This proposal has been greatly improved by the feedback from Félix Cloutier, Geoff Garen, Gábor Horváth, Frederick Kellison-Linn, Karl Wagner, and Xiaodi Wu.

0 commit comments

Comments
 (0)