Skip to content

Commit 11f5ff9

Browse files
committed
Replace example because withUnsafebufferPointerSimplified is weird
1 parent 97fae3c commit 11f5ff9

File tree

1 file changed

+43
-49
lines changed

1 file changed

+43
-49
lines changed

proposals/nnnn-strict-memory-safety.md

Lines changed: 43 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -61,56 +61,60 @@ public struct UnsafeBufferPointer<Element> { ... }
6161
This indicates that use of this type is not memory-safe. Any declaration that has `UnsafeBufferPointer` as part of its type is also unsafe, and would produce a warning under this strict safety mode, e.g.,
6262

6363
```swift
64-
extension Array {
65-
// warning on next line: reference to unsafe generic struct 'UnsafeBufferPointer'
66-
func withUnsafeBufferPointerSimplified<T>(_ body: (UnsafeBufferPointer<Element>) -> T) -> T {
67-
// ...
68-
}
69-
}
64+
// warning: reference to unsafe generic struct 'UnsafePointer'
65+
func sumIntBuffer(_ address: UnsafePointer<Int>?, _ count: Int) -> Int { ... }
7066
```
7167

7268
This warning can be suppressed by marking the function as `@unsafe`:
7369

7470
```swift
75-
extension Array {
76-
@unsafe
77-
func withUnsafeBufferPointerSimplified<T>(_ body: (UnsafeBufferPointer<Element>) -> T) -> T {
78-
// ...
79-
}
80-
}
71+
@unsafe
72+
func sumIntBuffer(_ address: UnsafePointer<Int>?, _ count: Int, _ start: Int) -> Int { ... }
8173
```
8274

83-
Code that does not enable safety checking will ignore the `@unsafe` attribute. Users of this function that also enable strict safety checking will see warnings when using it. For example:
75+
Users of this function that enable strict safety checking will see warnings when using it. For example:
8476

8577
```swift
8678
extension Array<Int> {
8779
func sum() -> Int {
88-
// warning: use of unsafe function 'withUnsafeBufferPointerSimplified'
89-
withUnsafeBufferPointerSimplified { buffer in
90-
// warning: use of function 'c_library_sum_function' with unsafe type 'UnsafePointer<Int>''.
91-
c_library_sum_function(buffer.baseAddress, buffer.count, 0)
80+
withUnsafeBufferPointer { buffer in
81+
// warning: use of unsafe function 'sumIntBuffer' and unsafe property 'baseAddress'
82+
sumIntBuffer(buffer.baseAddress, buffer.count, 0)
9283
}
9384
}
9485
}
9586
```
9687

97-
Both the call to `withUnsafeBufferPointerSimplified` (which is `@unsafe`) and the call to `c_library_sum_function` (which has a parameter of `@unsafe` type `UnsafePointer`) would trigger warnings about uses of unsafe constructs.
88+
Both the call to `sumIntBuffer` and access to the property `UnsafeBufferPointer.baseAddress` involve unsafe code, and therefore will produce a warning. Because `UnsafeBufferPointer` and `UnsafePointer` are `@unsafe` types, this code will get a warning regardless of whether the declarations were marked `@unsafe`, because having unsafe types in the signature of a declaration implies that they are `@unsafe`. This helps us identify more unsafe code even when the libraries we depend on haven't enabled strict safety checking themselves.
9889

99-
To suppress these warnings, both expressions must be marked with `unsafe` in the same manner as one would mark a throwing expression with `try` or an asynchronous expression with `async`. The warning-free version of this code is:
90+
To suppress these warnings, the expressions involving unsafe code must be marked with `unsafe` in the same manner as one would mark a throwing expression with `try` or an asynchronous expression with `async`. The warning-free version of this code is:
10091

10192
```swift
10293
extension Array<Int> {
10394
func sum() -> Int {
104-
unsafe withUnsafeBufferPointerSimplified { buffer in
105-
unsafe c_library_sum_function(buffer.baseAddress, buffer.count, 0)
95+
withUnsafeBufferPointer { buffer in
96+
// warning: use of unsafe function 'sumIntBuffer' and unsafe property 'baseAddress'
97+
unsafe sumIntBuffer(buffer.baseAddress, buffer.count, 0)
10698
}
10799
}
108100
}
109101
```
110102

111-
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`.
103+
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 `sumIntBuffer` is unsafe, as is the use of `buffer` and `buffer.baseAddress`, yet they are all covered by one `unsafe`.
104+
105+
Unlike `try`, `unsafe` doesn't propagate outward: we do *not* require that the `sum` function be marked `@unsafe` just because it has unsafe code in it. Similarly, the call to `withUnsafeBufferPointer` doesn't have to be marked as `unsafe` just because it has a closure that is unsafe. The programmer may choose to indicate that `sum` is unsafe, but the assumption is that unsafe behavior is properly encapsulated when using `unsafe` if the signature doesn't contain any unsafe types.
106+
107+
The function `Array.withUnsafeBufferPointer` has an unsafe type in its signature, because it passes an unsafe buffer pointer to its closure parameter. However, this function itself is addressing all of the memory-safety issues with providing such a pointer, and it's up to the closure itself to ensure that it is memory safe. Therefore, we mark `withUnsafeBufferPointer` with the `@safe` attribute to indicate that it iself is not introducing memory-safety issues:
108+
109+
```swift
110+
extension Array {
111+
@safe func withUnsafeBufferPointer<R, E>(
112+
_ body: (UnsafeBufferPointer<Element>) throws(E) -> R
113+
) throws(E) -> R
114+
}
115+
```
112116

113-
Unlike `try`, `unsafe` doesn't propagate outward: we do *not* require that the `sum` function be marked `@unsafe` just because it has unsafe code in it. Similarly, the call to `withUnsafeBufferPointerSimplified` is unsafe because it involves the `UnsafeBufferPointer` type, not because it was passed a closure containing `unsafe` behavior. The programmer may choose to indicate that `sum` is unsafe, but the assumption is that unsafe behavior is properly encapsulated when using `unsafe` if the signature doesn't contain any unsafe types. Additionally, the `@unsafe` attribute and `unsafe` expression is available for all Swift code, even if it doesn't itself enable the strict safety checking described in this proposal.
117+
The new attributes `@safe` and `@unsafe`, as well as the `unsafe` expression, are all available in Swift regardless of whether strict safety checking is enabled, and all code using these features retains the same semantics. Strict safety checking will *only* produce diagnostics.
114118

115119
### A larger example: `swapAt` on unsafe pointers
116120

@@ -566,8 +570,8 @@ In the proposed design, a function with no unsafe types in its signature is cons
566570
extension Array<Int> {
567571
// this function is considered safe
568572
func sum() -> Int {
569-
unsafe withUnsafeBufferPointerSimplified { buffer in
570-
unsafe c_library_sum_function(buffer.baseAddress, buffer.count, 0)
573+
withUnsafeBufferPointer { buffer in
574+
unsafe sumIntBuffer(buffer.baseAddress, buffer.count, 0)
571575
}
572576
}
573577
}
@@ -581,8 +585,8 @@ There are several options for such a suppression mechanism. An attribute form, `
581585
extension Array<Int> {
582586
// this function is considered safe
583587
func sum() -> Int {
584-
unsafe! withUnsafeBufferPointerSimplified { buffer in
585-
unsafe! c_library_sum_function(buffer.baseAddress, buffer.count, 0)
588+
withUnsafeBufferPointer { buffer in
589+
unsafe! sumIntBuffer(buffer.baseAddress, buffer.count, 0)
586590
}
587591
}
588592
}
@@ -592,34 +596,24 @@ This proposal chooses not to go down this path, because having a function signat
592596

593597
### `@safe(unchecked)` attribute to allow unsafe code
594598

595-
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:
596-
597-
```swift
598-
extension Array<Int> {
599-
@safe(unchecked)
600-
func sum() -> Int {
601-
withUnsafeBufferPointerSimplified { buffer in
602-
c_library_sum_function(buffer.baseAddress, buffer.count, 0)
603-
}
604-
}
605-
}
606-
```
607-
608-
This approach means fewer annotations for unsafe code, because one `@safe(unchecked)` covers the entire body of the function. That is a trade-off: it's less verbose, but it makes it much harder to identify exactly what parts of the implementation are actually unsafe. To reduce the amount of code covered by a `@safe(unchecked)` down to just the actual unsafe code, one would have to factor out the unsafe code into many small `@safe(unchecked)` functions, which could end up being quite verbose and make it harder to reason about the code itself.
609-
610-
This approach also implies that making a function `@unsafe` will suppress any diagnostics about uses of unsafe constructs within the body of that function. Rust's `unsafe` functions have this behavior, and it is the source of [some concern in that community](https://rust-lang.github.io/rfcs/2585-unsafe-block-in-unsafe-fn.html). Part of the issue there involves wanting to tightly scope the uses of unsafe code, but that discussion brings up another reason: it means that `unsafe` on a function actually has a dual role: it both says that the function is unsafe to use *and also* says that the function can freely use unsafe constructs in its definition.
599+
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. This has all of the same downsides as having `@unsafe` imply cover for all of the uses of unsafe code within the body of a function, albeit while providing a safe interface.
611600

612601
### `unsafe` blocks
613602

614-
The `unsafe` expression proposed here covers unsafe constructs within a single expression. For unsafe-heavy code, this can introduce a large number of `unsafe` keywords. There is an alternative formulation for acknowledging unsafe code that is used in some peer languages like C# and Rust: an `unsafe` block, which is a statement that suppresses diagnostics about uses of unsafe code within it. For example, the `sum` example could be written as follows:
603+
The `unsafe` expression proposed here covers unsafe constructs within a single expression. For unsafe-heavy code, this can introduce a large number of `unsafe` keywords. There is an alternative formulation for acknowledging unsafe code that is used in some peer languages like C# and Rust: an `unsafe` block, which is a statement that suppresses diagnostics about uses of unsafe code within it. For example:
615604

616605
```swift
617-
extension Array<Int> {
618-
func sum() -> Int {
606+
extension UnsafeMutableBufferPointer {
607+
@unsafe public func swapAt(_ i: Element, _ j: Element) {
608+
guard i != j else { return }
609+
precondition(i >= 0 && j >= 0)
610+
precondition(i < endIndex && j < endIndex)
619611
unsafe {
620-
withUnsafeBufferPointerSimplified { buffer in
621-
c_library_sum_function(buffer.baseAddress, buffer.count, 0)
622-
}
612+
let pi = (baseAddress! + i)
613+
let pj = (baseAddress! + j)
614+
let tmp = pi.move()
615+
pi.moveInitialize(from: pj, count: 1)
616+
pj.initialize(to: tmp)
623617
}
624618
}
625619
}

0 commit comments

Comments
 (0)