Skip to content

Commit 7320810

Browse files
author
Gabor Horvath
committed
Document features around safe interoperability
This PR adds a new top-level page describing how to interact with C++ from strict memory safe Swift.
1 parent bedf6b2 commit 7320810

File tree

2 files changed

+303
-0
lines changed

2 files changed

+303
-0
lines changed

_data/documentation.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
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.
5157
- title: Value and Reference types
5258
url: /documentation/articles/value-and-reference-types.html
5359
description: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
---
2+
layout: page
3+
title: Mixing Strict Memory Safe Swift and C++
4+
official_url: https://swift.org/documentation/cxx-interop/safe-interop/
5+
redirect_from:
6+
- /documentation/cxx-interop/safe-interop.html
7+
---
8+
9+
## Table of Contents
10+
{:.no_toc}
11+
12+
* TOC
13+
{:toc}
14+
15+
## Introduction
16+
17+
[Strict memory safe Swift](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.
19+
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
21+
but they provide more value in strict memory safe mode.
22+
23+
* * *
24+
25+
<div class="info" markdown="1">
26+
C++ interoperability is an actively evolving feature of Swift.
27+
Future releases of Swift might change how Swift and C++
28+
interoperate,
29+
as the Swift community gathers feedback from real world adoption of C++
30+
interoperability in mixed Swift and C++ codebases.
31+
Please provide the feedback that you have on the
32+
[Swift forums](https://forums.swift.org/c/development/c-interoperability/), or
33+
by filing an [issue on GitHub](https://github.com/swiftlang/swift/issues/new/choose).
34+
Future changes to the design or functionality of C++ interoperability will not
35+
break code in existing codebases [by default](#source-stability-guarantees-for-mixed-language-codebases).
36+
</div>
37+
38+
## Overview
39+
40+
Swift provides memory safety with a combination of language affordances and runtime checking.
41+
However, Swift also deliberately includes some unsafe constructs, such as the Unsafe pointer types in the standard library.
42+
Swift occasionally needs additional information that is not present in the C++ type and API declarations
43+
to safely interface with them. This document describes how such code need to be annotated.
44+
45+
### Annotating foreign types
46+
47+
Types imported from C++ are considered foreign to Swift. Many of these types are considered safe,
48+
including the built-in integral types like `int`, some standard library types like `std::string`,
49+
and aggregate types that are using safe types are building blocks.
50+
51+
On the other hand, some C++ types are imported as unsafe by default. Consider the following C++ type
52+
and APIs:
53+
54+
```c++
55+
class StringRef {
56+
public:
57+
StringRef() : ptr(nullptr), len(0) {}
58+
59+
std::string toString() const;
60+
private:
61+
const char* ptr;
62+
size_t len;
63+
};
64+
65+
std::string normalize(const std::string& path);
66+
67+
// The path needs to be normalized.
68+
StringRef fileName(const std::string& path);
69+
```
70+
71+
Let's try to use them from Swift with strict memory safety enabled:
72+
73+
```swift
74+
func getFileName(_ path: borrowing std.string) -> StringRef {
75+
let normalizedPath = normalize(path)
76+
return fileName(normalizedPath)
77+
}
78+
```
79+
80+
Building this code will emit a warning that the `fileName` call is unsafe because
81+
it references the unsafe type `StringRef`. Swift considers `StringRef` unsafe because
82+
it has a pointer member. Types like `StringRef` can dangle, we need to take extra
83+
care using them, making sure the referenced buffer outlives the `StringRef` object.
84+
85+
Swift's [non-escapable types](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0446-non-escapable.md)
86+
can also have lifetime dependencies, just like `StringRef`. However, the Swift compiler
87+
can track these dependencies and enforce safety at compile time. To import `StringRef`
88+
as a safe type we need to mark it as not escapable.
89+
90+
```c++
91+
class SWIFT_NONESCAPABLE StringRef {
92+
```
93+
94+
### Annotating APIs
95+
96+
Building the code again will emit a new diagnostic for the `fileName` function about
97+
missing lifetime annotations. Functions returning non-escapable types need annotations
98+
to describe their lifetime contracts via [lifetimebound](https://clang.llvm.org/docs/AttributeReference.html#id11)
99+
and [lifetime_capture_by](https://clang.llvm.org/docs/AttributeReference.html#lifetime-capture-by) annotations.
100+
101+
```c++
102+
StringRef fileName(const std::string& path [[clang::lifetimebound]]);
103+
```
104+
105+
This annotation describes that the returned `StringRef` value has the same lifetime as the argument
106+
of the `fileName` function.
107+
108+
Building the project again reveals a lifetime error in the Swift function:
109+
110+
```swift
111+
func getFileName(_ path: borrowing std.string) -> StringRef {
112+
let normalizedPath = normalize(path)
113+
// error: lifetime-dependent value escapes local scope
114+
// note: depends on `normalizedPath`
115+
return fileName(normalizedPath)
116+
}
117+
```
118+
119+
The value returned by `fileName` will dangle after the lifetime of `normalizedPath` ends.
120+
We can fix this error by pushing the task of normalizing a path to the callee:
121+
122+
```swift
123+
// Path needs to be normalized.
124+
func getFileName(_ path: borrowing std.string) -> StringRef {
125+
return fileName(normalizedPath)
126+
}
127+
```
128+
129+
Or we could return a self-contained value like `std.string` instead of a dangling `StringRef`:
130+
131+
```swift
132+
func getFileName(_ path: borrowing std.string) -> std.string {
133+
let normalizedPath = normalize(path)
134+
let ref = fileName(normalizedPath)
135+
return ref.toString()
136+
}
137+
```
138+
139+
## Escapability annotations in detail
140+
141+
All of the unannotated types are imported as `Escapable` to maintain backward
142+
compatibility. This might change in the future under a new interoperability version.
143+
We can import a type as `~Escapable` to Swift by adding the `SWIFT_NONESCAPABLE`
144+
annotation.
145+
146+
```c++
147+
struct SWIFT_NONESCAPABLE View {
148+
View() : member(nullptr) {}
149+
View(const int *p) : member(p) {}
150+
View(const View&) = default;
151+
private:
152+
const int *member;
153+
};
154+
```
155+
156+
Moreover, we can explicitly mark types as `Escapable` using the `SWIFT_ESCAPABLE`
157+
annotation.
158+
159+
```c++
160+
struct SWIFT_ESCAPABLE Owner {
161+
...
162+
};
163+
```
164+
165+
The main reason for explicitly annotating a type as `SWIFT_ESCAPABLE` to make sure
166+
it is considered as a safe type when used from Swift. Functions returning escapable
167+
types do not need lifetime annotations.
168+
169+
Escapability annotations can be attached to types via APINotes.
170+
171+
```
172+
Tags:
173+
- Name: NonEscapableType
174+
SwiftEscapable: false
175+
- Name: EscapableType
176+
SwiftEscapable: true
177+
```
178+
179+
In case of template instantiations the escapability of a type can depend on the
180+
template arguments:
181+
182+
```c++
183+
MyList<View> f();
184+
MyList<Owner> g();
185+
```
186+
187+
In this example, `MyList<View>` should be imported as `~Escapable` while `MyList<Owner>`
188+
should be imported as `Escapable`. This can be achieved via conditional escapability
189+
annotations.
190+
191+
```
192+
template<typename T>
193+
struct SWIFT_ESCAPABLE_IF(T) MyList {
194+
...
195+
};
196+
```
197+
198+
## Lifetime annotations in detail
199+
200+
The `lifetimebound` annotation can be used to annotate methods, free functions, and constructors.
201+
It can annotate dependence on regular arguments or on the `this` object.
202+
203+
```c++
204+
struct SWIFT_NONESCAPABLE View {
205+
View() : member(nullptr) {}
206+
View(const int *p [[clang::lifetimebound]]) : member(p) {}
207+
View(const View&) = default;
208+
private:
209+
const int *member;
210+
};
211+
212+
struct Owner {
213+
int data;
214+
215+
View handOutView() const [[clang::lifetimebound]] {
216+
return View(&data);
217+
}
218+
};
219+
220+
View getView(const Owner& owner [[clang::lifetimebound]]) {
221+
return View(&owner.data);
222+
}
223+
224+
View getViewFromFirst(const Owner& owner [[clang::lifetimebound]], const Owner& owner2) {
225+
return View(&owner.data);
226+
}
227+
228+
View getViewFromEither(View view1 [[clang::lifetimebound]], View view2 [[clang::lifetimebound]]) {
229+
if (coinFlip)
230+
return view1;
231+
else
232+
return view2;
233+
}
234+
```
235+
236+
Occasionally, a function might return a non-escapable type that in fact has no dependency on any other values.
237+
These types might point to static data or might represent an empty sequence or lack of data.
238+
Such functions need to be annotated with `SWIFT_RETURNS_INDEPENDENT_VALUE`:
239+
240+
```c++
241+
View returnsEmpty() SWIFT_RETURNS_INDEPENDENT_VALUE {
242+
return View();
243+
}
244+
```
245+
246+
The default constructor of a type is assumed to create an independent value.
247+
248+
We can annotate `lifetimebound` APIs via APINotes. The -1 index represents the this position.
249+
Note that APINotes have some limitations around C++, they do not support overloaded functions.
250+
251+
```
252+
Tags:
253+
- Name: MyClass
254+
Methods:
255+
- Name: annotateThis
256+
Parameters:
257+
- Position: -1
258+
Lifetimebound: true
259+
- Name: methodToAnnotate
260+
Parameters:
261+
- Position: 0
262+
Lifetimebound: true
263+
```
264+
265+
We can use `lifetime_capture_by` annotations for output arguments.
266+
267+
```c++
268+
void copyView(View view1 [[clang::lifetime_capture_by(view2)]], View &view2) {
269+
view2 = view1;
270+
}
271+
272+
struct SWIFT_NONESCAPABLE CaptureView {
273+
CaptureView() : view(nullptr) {}
274+
CaptureView(View p [[clang::lifetimebound]]) : view(p) {}
275+
276+
void captureView(View v [[clang::lifetime_capture_by(this)]]) {
277+
view = v;
278+
}
279+
280+
void handOut(View &v) const [[clang::lifetime_capture_by(v)]] {
281+
v = view;
282+
}
283+
284+
View view;
285+
};
286+
```
287+
288+
All of the non-escapable inputs need lifetime annotations for a function to be
289+
considered safe. If an input never escapes from the called function we can use
290+
the `noescape` annotation.
291+
292+
```c++
293+
void is_palindrome(std::span<int> s [[clang::noescape]]);
294+
```
295+
296+
## Convenience overloads for annotated spans and pointers
297+

0 commit comments

Comments
 (0)