Skip to content

Commit 753f869

Browse files
author
Gabor Horvath
committed
Extend the later part of the documentation a bit.
1 parent f6adb06 commit 753f869

File tree

3 files changed

+135
-45
lines changed

3 files changed

+135
-45
lines changed

_data/documentation.yaml

-6
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,6 @@
4848
description: |
4949
Swift has support for bidirectional interoperability with C++.
5050
A great variety of C++ APIs can be called directly from Swift, and select Swift APIs can be used from C++.
51-
- title: Mixing Strict Memory Safe Swift and C++
52-
url: /documentation/cxx-interop/safe-interop
53-
description: |
54-
Strict memory safe Swift makes code more auditable for memory safety errors by explicitly delineating potentially
55-
unsafe code from safe code. Using the right annotations we can safely interact with C++ from Swift without the need
56-
to sprinkle `unsafe` before every C++ construct.
5751
- title: Value and Reference types
5852
url: /documentation/articles/value-and-reference-types.html
5953
description: |

documentation/cxx-interop/index.md

+6
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,12 @@ automatically.
13311331

13321332
## Working with C++ References and View Types in Swift
13331333

1334+
Swift has a new [safe interoperability](https://www.swift.org/documentation/cxx-interop/safe-interop)
1335+
model under development that makes code auditable for memory safety errors by explicitly delineating potentially
1336+
unsafe code from safe code and introducing ways to annotate lifetime contracts. We recommend giving this
1337+
new mode of interoperability a try as the rest of this section might be subsumed by these new features under
1338+
a new interoperability version in the future.
1339+
13341340
As outlined
13351341
[earlier](#member-functions-returning-references-are-unsafe-by-default),
13361342
member functions that return references, pointers, or certain structures/classes that

documentation/cxx-interop/safe-interop/index.md

+129-39
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ redirect_from:
1414

1515
## Introduction
1616

17-
Swift's [strict memory safety mode](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md)
18-
is a new feature in Swift 6.2 to make code easier to audit for memory safety.
1917
This document describes how to ergonomically interact with Swift by importing
20-
C++ construct safely. All of the features in this document work in regular Swift
18+
C++ construct safely. Swift's [strict memory safety mode](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0458-strict-memory-safety.md)
19+
is a new feature under development to make code easier to audit for memory safety.
20+
All of the features in this document work in regular Swift
2121
but they provide more value in strict memory safe mode.
2222

2323
* * *
@@ -40,17 +40,28 @@ break code in existing codebases [by default](#source-stability-guarantees-for-m
4040
Swift provides memory safety with a combination of language affordances and runtime checking.
4141
However, Swift also deliberately includes some unsafe constructs, such as the `UnsafePointer` and `UnsafeMutablePointer`
4242
types in the standard library.
43-
Swift occasionally needs additional information that is not present in the C++ type and API declarations
43+
In some cases, Swift needs additional information that is not present in the C++ type and API declarations
4444
to safely interface with them. This document describes how such code needs to be annotated.
4545

4646
### Annotating foreign types
4747

48-
Types imported from C++ are considered foreign to Swift. Many of these types are considered safe,
49-
including the built-in integral types like `int`, some standard library types like `std::string`,
50-
and aggregate types built from other safe types.
51-
52-
On the other hand, some C++ types are imported as unsafe by default. Consider the following C++ type
53-
and APIs:
48+
Types imported from C++ are considered foreign to Swift.
49+
Types imported from C++ are considered foreign to Swift. Normally, most C++ types are imported into Swift
50+
without any restriction. However, a small set of C++ APIs e.g. pointers/references and methods returning
51+
pointers will be imported as unsafe (section [Working with C++ references and view types in Swift](https://www.swift.org/documentation/cxx-interop/#working-with-c-references-and-view-types-in-swift)
52+
explains this in more detail.) Under the strict memory safe mode, the compiler will flip the polarity and
53+
treat all types that are not known to be safe as unsafe, and will diagnose uses of them. In this section,
54+
we will show how to annotate unsafe C++ types so that they can be accessed safely and correctly from Swift.
55+
Note that the features here are agnostic to whether strictly-safe mode is on or off. When the strictly safe
56+
mode is on, the compiler warnings can serve as a guide to properly annotate C++ types and also help ensure
57+
that the code doesn't use unsafe APIs anywhere. When the strictly-memory-safe mode is off, it is still
58+
recommended to adopt these annotation whereever appropriate, especially on C++ types that are potentially
59+
lifetime dependent on other objects.
60+
61+
Under strictly-memory-safe mode the built-in integral types like `int`, some standard library types like `std::string`,
62+
and aggregate types built from other safe types are considered safe. Whereas all other unannotated types
63+
are considered unsafe. Let's see what happens when we are trying to use an unannotated type in
64+
strictly-safe mode. Consider the following C++ type and APIs:
5465

5566
```c++
5667
class StringRef {
@@ -143,28 +154,25 @@ contracts helping us to write code that is free of memory safety errors.
143154

144155
## Escapability annotations in detail
145156

146-
Currently, unannotated types are imported as `Escapable` to maintain backward
157+
Under the strictly-safe mode, even though compiler warns on unannotated types,
158+
they are imported as if they are `Escapable` to maintain backward
147159
compatibility. This might change in the future under a new interoperability version.
148160
We have already seen that we can import a type as `~Escapable` to Swift by adding
149161
the `SWIFT_NONESCAPABLE` annotation:
150162

151163
```c++
152164
struct SWIFT_NONESCAPABLE View {
153-
View() : member(nullptr) {}
154165
View(const int *p) : member(p) {}
155-
View(const View&) = default;
156166
private:
157167
const int *member;
158168
};
159169
```
160170
161171
Moreover, we can explicitly mark types as `Escapable` using the `SWIFT_ESCAPABLE`
162-
annotation:
172+
annotation to express that they are not lifetime dependent on any other values:
163173
164174
```c++
165-
struct SWIFT_ESCAPABLE Owner {
166-
...
167-
};
175+
struct SWIFT_ESCAPABLE Owner { ... };
168176
```
169177

170178
The main reason for explicitly annotating a type as `SWIFT_ESCAPABLE` is to make sure
@@ -193,28 +201,63 @@ In this example, `MyList<View>` should be imported as `~Escapable` while `MyList
193201
should be imported as `Escapable`. This can be achieved via conditional escapability
194202
annotations:
195203

196-
```
204+
```c++
197205
template<typename T>
198206
struct SWIFT_ESCAPABLE_IF(T) MyList {
199207
...
200208
};
201209
```
202210

211+
Here, instantiations of `MyList` are imported as `Escapable` when `T` is substituted
212+
with an `Escapable` type.
213+
214+
The `SWIFT_ESCAPABLE_IF` macro can take multiple template parameters:
215+
216+
```c++
217+
template<typename F, typename S>
218+
struct SWIFT_ESCAPABLE_IF(F, S) MyPair {
219+
F first;
220+
S second;
221+
};
222+
```
223+
224+
`MyPair` instantiations are only imported as `Escapable` if both template arguments
225+
are `Escapable`.
226+
227+
`Escapable` types cannot have `~Escapable` fields. The following code snippet will
228+
trigger a compiler error:
229+
230+
```c++
231+
struct SWIFT_NONESCAPABLE View { ... };
232+
struct SWIFT_ESCAPABLE Owner {
233+
View v;
234+
};
235+
```
236+
237+
Escapability annotations will not only help the Swift compiler to import C++ types
238+
safely, it will also help discover missing lifetime annotations as all `~Escapable`
239+
parameters and return values need to be annotated in an API to make its use safe in
240+
Swift.
241+
203242
## Lifetime annotations in detail
204243
205-
The `lifetimebound` attribute can be used to annotate code in various scenarios.
206-
On a constructor, it describes the lifetime of the created object:
244+
The `lifetimebound` attribute on a function parameter or implicit object parameter
245+
indicates that the returned object's lifetime could end when any of the `lifetimebound`
246+
annotated parameters' lifetime ended.
247+
This annotation a constructor describes the lifetime of the created object:
207248
208249
```c++
209250
struct SWIFT_NONESCAPABLE View {
210251
View(const int *p [[clang::lifetimebound]]) : member(p) {}
211-
private:
212-
const int *member;
252+
...
213253
};
214254
```
215255

256+
In this example, the object initialized by the `View` constructor has the same
257+
lifetime as the input argument of the constructor.
258+
216259
In case the attribute is after the method signature, the returned object has
217-
the same lifetime as the `this` object.
260+
the same lifetime as the implicit `this` parameter.
218261

219262
```c++
220263
struct Owner {
@@ -226,27 +269,28 @@ struct Owner {
226269
};
227270
```
228271

229-
In case the attribute is applied to a subset of the formal parameters, the return
272+
Consider a call site like `View v = o.handOutView()`. The `v` object has the same lifetime
273+
as `o`.
274+
275+
In case the attribute is applied to a subset of the parameters, the return
230276
value might depend on the corresponding arguments:
231277

232278
```c++
233-
View getView(const Owner& owner [[clang::lifetimebound]]) {
234-
return View(&owner.data);
235-
}
236-
237-
View getViewFromFirst(const Owner& owner [[clang::lifetimebound]], const Owner& owner2) {
238-
return View(&owner.data);
239-
}
240-
241-
View getViewFromEither(View view1 [[clang::lifetimebound]], View view2 [[clang::lifetimebound]]) {
279+
View getOneOfTheViews(const Owner& owner1 [[clang::lifetimebound]], const Owner& owner2
280+
View view1 [[clang::lifetimebound]], View view2 [[clang::lifetimebound]]) {
281+
if (coinFlip)
282+
return View(&owner1.data);
242283
if (coinFlip)
243284
return view1;
244285
else
245286
return view2;
246287
}
247288
```
248289
249-
Occasionally, a function might return a non-escapable type that in fact has no dependency on any other values.
290+
Here, the returned `View`'s lifetime depends on `owner`, `view1`, and `view2` but it cannot
291+
depend on `owner2`.
292+
293+
Occasionally, a function might return a non-escapable type that has no dependency on any other values.
250294
These types might point to static data or might represent an empty sequence or lack of data.
251295
Such functions need to be annotated with `SWIFT_RETURNS_INDEPENDENT_VALUE`:
252296
@@ -276,17 +320,25 @@ Tags:
276320

277321
Note that APINotes have some limitations around C++, they do not support overloaded functions.
278322

279-
We can use `lifetime_capture_by` annotations for output arguments.
323+
While `lifetimebound` always describes the lifetime dependencies of the return value (or
324+
the constructed object in case of constructors), we can use can use `lifetime_capture_by`
325+
annotation to descibe the lifetime of other output values, like output/inout arguments
326+
or globals.
280327

281328
```c++
282329
void copyView(View view1 [[clang::lifetime_capture_by(view2)]], View &view2) {
283330
view2 = view1;
284331
}
332+
```
285333
286-
struct SWIFT_NONESCAPABLE CaptureView {
287-
CaptureView() : view(nullptr) {}
288-
CaptureView(View p [[clang::lifetimebound]]) : view(p) {}
334+
In this example, `view2` will have get all of the lifetime dependencies of `view1`
335+
after a call to `copyView`. a
336+
337+
We can annotate dependency captured by the implicit `this` object, or
338+
an inout argument capturing `this`:
289339
340+
```c++
341+
struct SWIFT_NONESCAPABLE CaptureView {
290342
void captureView(View v [[clang::lifetime_capture_by(this)]]) {
291343
view = v;
292344
}
@@ -301,11 +353,49 @@ struct SWIFT_NONESCAPABLE CaptureView {
301353

302354
All of the non-escapable inputs need lifetime annotations for a function to be
303355
considered safe. If an input never escapes from the called function we can use
304-
the `noescape` annotation.
356+
the `noescape` annotation:
305357

306358
```c++
307359
void is_palindrome(std::span<int> s [[clang::noescape]]);
308360
```
309361
362+
While the annotations in this section are powerful, they cannot express all of
363+
the lifetime contracts. APIs with inexpressible contracts can be used from Swift,
364+
but they are imported as unsafe APIs and need extra care from the developers
365+
to manually guarantee safety.
366+
310367
## Convenience overloads for annotated spans and pointers
311368
369+
C++ APIs often using standard library types or other constructs like a
370+
pointer and a size to represent buffers that have Swift equivalents like
371+
Swift's `Span` type. These Swift types have additional requirements and
372+
guarantees. When these properties are properly annotated on the C++ side,
373+
the Swift compiler can introduce safe convenience functions to make
374+
interacting with the C++ APIs as effortless as if they were written in Swift.
375+
376+
### C++ span support
377+
378+
APIs taking/returning C++'s `std::span` with sufficient lifetime
379+
annotations will automatically get overloads taking/returning Swift
380+
`Span`.
381+
382+
The following table summarizes the generated convenience overloads:
383+
384+
```c++
385+
using IntSpan = std::span<const int>;
386+
using IntVec = std::vector<int>;
387+
```
388+
389+
| C++ API | Generated Swift overload |
390+
| --------------------------------------------------------- | -------------------------------------------------------------------- |
391+
| `void takeSpan(IntSpan x [[clang::noescape]]);` | `func takeSpan(_ x: Span<Int32>)` |
392+
| `IntSpan changeSpan(IntSpan x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: Span<Int32>) -> Span<Int32>` |
393+
| `IntSpan changeSpan(IntVec& x [[clang::lifetimebound]]);` | `@lifetime(x) func changeSpan(_ x: borrowing IntVec) -> Span<Int32>` |
394+
| `IntSpan Owner::getSpan() [[clang::lifetimebound]];` | `@lifetime(self) func getSpan() -> Span<Int32>` |
395+
396+
These transformations only support top level `std::span`s, we do not
397+
transform the nested cases. A `std::span` of a non-const type `T` will
398+
be transformed to `MutableSpan<T>` on the Swift wide.
399+
400+
### Annotated pointers
401+

0 commit comments

Comments
 (0)