Skip to content

Commit 662120e

Browse files
committed
Introduce more about lifetimebound and C++
1 parent 1699c5a commit 662120e

File tree

1 file changed

+34
-7
lines changed

1 file changed

+34
-7
lines changed

visions/memory-safety.md

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@ print(span.first ?? 0)
101101

102102
## Expressing memory-safe interfaces for the C family of languages
103103

104-
The C family of languages do not provide memory safety along any of the dimensions described in this document. As such, a Swift program that makes use of C APIs is never fully “memory safe” in the strict sense, because any C code called from Swift could undermine the memory safety guarantees Swift is trying to provide. Requiring that all such C code be rewritten in Swift would go against Swift’s general philosophy of incremental adoption into existing ecosystems. Therefore, this document proposes a different strategy: code written in Swift will be auditably memory-safe so long as the C APIs it uses follow reasonable conventions with respect to memory safety. As such, writing new code (or incrementally rewriting code from the C family) will not introduce new memory safety bugs, so that adopting Swift in an existing code base will incrementally improve on memory safety.
104+
The C family of languages do not provide memory safety along any of the dimensions described in this document. As such, a Swift program that makes use of C APIs is never fully “memory safe” in the strict sense, because any C code called from Swift could undermine the memory safety guarantees Swift is trying to provide. Requiring that all such C code be rewritten in Swift would go against Swift’s general philosophy of incremental adoption into existing ecosystems. Therefore, this document proposes a different strategy: code written in Swift will be auditably memory-safe so long as the C APIs it uses follow reasonable conventions with respect to memory safety. As such, writing new code (or incrementally rewriting code from the C family) will not introduce new memory safety bugs, so that adopting Swift in an existing code base will incrementally improve on memory safety. This approach is complementary to any improvements made to memory safety within the C family of languages, such as [bounds-safety checks for C](https://clang.llvm.org/docs/BoundsSafety.html) or [C++ standard library hardening](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3471r0.html).
105105

106106
In the C family of languages, the primary memory safety issue for APIs is the widespread use of pointers that have neither lifetime annotations (who owns the pointer?) nor bounds annotations (how many elements does it point to?). As such, the pointers used in C APIs are reflected in Swift as unsafe pointer types, as shown above with `memcpy` .
107107

108108
Despite the lack of this information, C APIs often follow a reasonable set of conventions that make them usable in Swift without causing memory-safety problems. Swift has a long history of utilizing annotations in C headers to describe these conventions and improve the projection of C APIs into Swift, including:
109109

110110
* Nullability annotations (`_Nullable`, `_Nonnull`) that describe what values can be NULL, and affects whether a C type is reflected as optional in Swift.
111-
* Non-escaping annotations (e.g., `__attribute__((noescape))`) on function/block pointer parameters, which results in them being imported as non-escaping function parameters.
111+
* Non-escaping annotations (e.g., `__attribute__((noescape))`) on block pointer parameters, which results in them being imported as non-escaping function parameters.
112112
* `@MainActor` and `Sendable` annotations on C APIs that support Swift 6’s data-race safety model.
113113

114114
To provide safer interoperability with C APIs, additional annotations can be provided in C that Swift can use to project those C APIs into Swift APIs without any use of unsafe pointers. For example, the Clang [bounds-safety attributes](https://clang.llvm.org/docs/BoundsSafety.html) allow one to express when a C pointer’s size is described by another value:
@@ -148,18 +148,45 @@ The `average` function is now expressing that it takes in a `double` pointer ref
148148
func average(_ ptr: Span<Double>) -> Double
149149
```
150150

151-
More expressive Swift lifetime features can also have corresponding C annotations, allowing more C semantics to be reflected into safe APIs in Swift. For example, consider a C function that finds the minimal element in an array and returns a pointer to it:
151+
More expressive Swift lifetime features can also have corresponding C annotations, allowing more C APIs to be reflected into safe APIs in Swift. For example, consider a C function that finds the minimal element in an array and returns a pointer to it:
152152

153153
```cpp
154154
const double *min_element(const double *__counted_by(N) __attribute__((noescape)) ptr, int N);
155155
```
156156
157-
The returned pointer will point into the buffer passed in, so its lifetime is tied to that of the pointer argument. The aforementioned [lifetime dependencies proposal](https://github.com/swiftlang/swift-evolution/pull/2305) allows this kind of dependency to be expressed in Swift, where the resulting non-escaping value (e.g., a `Span` containing one element) has its lifetime tied to the input argument.
157+
The returned pointer will point into the buffer passed in, so its lifetime is tied to that of the pointer argument. The aforementioned [lifetime dependencies proposal](https://github.com/swiftlang/swift-evolution/pull/2305) allows this kind of dependency to be expressed in Swift, where the resulting non-escaping value (e.g., a `Span` containing one element) has its lifetime tied to the input argument. Clang provides a [`lifetimebound`](https://clang.llvm.org/docs/AttributeReference.html#id11) attribute that expresses when a return value refers into memory associated with one of the parameters, which offers one way to express this lifetime relationship for C APIs:
158158
159-
C++ offers a number of further opportunities for improved safety by modeling lifetimes. For example, `std::list<T>` has a `front()` method that returns a reference to the element at the front of the list:
159+
```c
160+
const double * _Nullable __counted_by(1)
161+
min_element(const double *__counted_by(N) __attribute__((noescape)) __attribute__((lifetimebound)) ptr, int N);
162+
```
163+
164+
The result coudl be the following memory-safe Swift API:
165+
166+
```swift
167+
@lifetime(ptr) func min_element(_ ptr: Span<Double>) -> Span<Double>?
168+
```
169+
170+
### Affordances for C++
171+
172+
C++ offers a number of further opportunities for improved safety by modeling lifetimes. For example, `std::vector<T>` has a `front()` method that returns a reference to the element at the front of the vector:
160173

161174
```cpp
162-
T& front();
175+
const T& front() const;
176+
```
177+
178+
The returned reference is valid so long as the vector instance still exists and has not been modified since the call to `front()`. Describing that lifetime dependency in C++ (for example, with the aforementioned `lifetimebound` attribute) would lead to a safe mapping of this API into Swift without the need to introduce an extra copy of the returned element, improving both safety and, potentially, performance.
179+
180+
The C++ [`std::span`](https://en.cppreference.com/w/cpp/container/span) type is similar to the Swift `Span` type, in that it also carries both a pointer and bounds to describe a region of memory. However, `std::span` doesn't provide lifetime safety, so it is essentially an unsafe type from the Swift perspective. The same C attributes that provide lifetime safety for C pointers and references could be applied to `std::span` instances to provide safe Swift projections of C++ APIs. For example, the following annotated C++ API:
181+
182+
```c++
183+
std::span<char> substring_match(std::span<char> sequence [[clang::lifetimebound]], std::span<char> subsequence [[clang::noescape]]);
184+
```
185+
186+
could be imported into Swift as:
187+
188+
```swift
189+
@lifetime(sequence)
190+
func substring_match(_ sequence: Span<CChar>, _ subsequence: Span<CChar>) -> Span<CChar>
163191
```
164192

165-
The returned reference is valid so long as the list is valid, i.e., its lifetime depends on the `this` parameter. Describing that lifetime dependency in C++ can lead to a safe mapping of this API into Swift without the need to introduce an extra copy of the element.

0 commit comments

Comments
 (0)