Skip to content

[interop][SwiftToCxx] update reverse interop user guide for new enum design #60524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 12, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 83 additions & 24 deletions docs/CppInteroperability/UserGuide-CallingSwiftFromC++.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

A Swift library author might want to expose their interface to C++, to allow a C++ codebase to interoperate with the Swift library. This document describes how this can be accomplished, by first describing how Swift can expose its interface to C++, and then going into the details on how to use Swift APIs from C++.

**NOTE:** This is a work-in-progress, living guide document for how Swift APIs can be imported and used from C++.

**NOTE:** This is a work-in-progress, living guide document for how Swift APIs can be imported and used from C++. This document reflects the current state of the experimental design, and it will evolve over time
as this feature will go through Swift's evolution process. This document does not specify the final target
design for the Swift to C++ interoperability layer.

**NOTE:** This document does not go over the following Swift language features yet:

Expand Down Expand Up @@ -71,7 +74,7 @@ Fundamental primitive types have a C++ fundamental type that represents them in
|--- |--- |--- |--- |--- |
|Void (or no return) |void | | | |
|Int |swift::Int |ptrdiff_t |long or long long (windows) |YES |
|UInt |size_t | |unsigned long or unsigned long long (windows) |YES |
|UInt |swift::UInt | size_t |unsigned long or unsigned long long (windows) |YES |
|Float |float | | | |
|Double |double | | | |
| | | | | |
Expand Down Expand Up @@ -424,7 +427,7 @@ int main() {

## Using Swift Enumeration Types

A Swift enumeration is imported as class in C++. That allows C++ to invoke methods and access properties that the enumeration provides. Each enumeration case that doesn’t have associated value is exposed as a static variable in the structure.
A Swift enumeration is imported as class in C++. That allows C++ to invoke methods and access properties that the enumeration provides. Each enumeration case is represented by a static variable that can be used in a switch to match the case of the enum, and to construct new enums values as well.

For example, given the following enum:

Expand All @@ -444,32 +447,44 @@ The following interface will be generated:
// "Navigation-Swift.h" - C++ interface for Swift's Navigation module.
class CompassDirection {
public:
static const CompassDirection north;
static const CompassDirection south;
static const CompassDirection east;
static const CompassDirection west;
static const struct { ... } north;
static const struct { ... } south;
static const struct { ... } east;
static const struct { ... } west;
private:
// type representation details.
...
};
```

This will let you construct enumeration values from C++ using the C++ call operator on the case:

```c++
#include "Navigation-Swift.h"

void testConstructEnumValue() {
auto direction = CompassDirection::north();
}
```

### Matching Swift Enumeration Values with a C++ Switch Statement

Swift’s enumerations can not be used directly in a switch, as C++ does not allow a `switch` to operate on C++ classes. However, For Swift enumerations that have an underlying integer representation, the generated C++ interface provides a convenience C++ enum called `cases` inside of the generated C++ class that represents the enumeration. This C++ enum can then be used in a switch, as the class that represents the enumeration implicitly converts to it. The `cases` C++ enum allows us to switch over the `CompassDirection` class from the example above in the following manner:
The C++ values that correspond to Swift enumeration case values can be used directly inside the switch statement. The generated C++ interface provides a convenience C++ enum called cases inside of the generated C++ class that represents the enumeration that the switch actually operates over. This C++ enum can then be used in a switch, as the class that represents the enumeration implicitly converts to it, and so do the C++ case values. This allows us to switch over the CompassDirection class from the example above in the following manner:

```c++
#include "Navigation-Swift.h"
using namespace Navigation;

CompassDirection getOpposite(CompassDirection cd) {
switch (cd) { // implicit conversion to CompassDirection::cases
using enum CompassDirection::cases; // allow name lookup to find enum cases.
case north:
return CompassDirection::south;
case south:
return CompassDirection::north;
case east:
return CompassDirection::west;
case west:
return CompassDirection::east;
case CompassDirection::north:
return CompassDirection::south();
case CompassDirection::south:
return CompassDirection::north();
case CompassDirection::east:
return CompassDirection::west();
case CompassDirection::west:
return CompassDirection::east();
}
}
```
Expand Down Expand Up @@ -533,6 +548,9 @@ Will get a C++ interface that resembles this class:
class Barcode {
public:
Barcode() = delete;

static const struct { ... } qrCode;
static const struct { ... } upc;

bool isUpc() const;

Expand All @@ -547,29 +565,70 @@ public:

// Extracts an associated value from Barcode.qrCode enum case
swift::String getQrCode() const;

static Barcode initUpc(swift::Int, swift::Int, swift::Int, swift::Int);
static Barcode initQrCode(swift::String);
};
```

The C++ user of this enumeration can then use it by checking the type of the value and getting the associated value using the `is` and `get` member functions:
The C++ user of this enumeration can then use it by checking the type of the value in a switch and getting the associated value using the get member functions:

```c++
#include "Store-Swift.h"
using namespace Store;

Barcode normalizeBarcode(Barcode barcode) {
if (barcode.isQrCode()) {
switch (barcode) {
case Barcode::qrCode: {
auto qrCode = barcode.getQrCode();
swift::Array<swift::Int> loadedBarcode = loadQrCode(qrCode);
return Barcode::initUpc(loadedBarcode[0], loadedBarcode[1], loadedBarcode[2], loadedBarcode[3]);
return Barcode::upc(loadedBarcode[0], loadedBarcode[1], loadedBarcode[2], loadedBarcode[3]);
}
case Barcode::upc:
return barcode;
}
}
```

return barcode;
The use of a `get` associated value accessor for an invalid enum case for the given
enum value will abort the program.

### Resilient Enums

A resilient Swift enumeration value could represent a case that's unknown to the client.
Swift forces the client to check if the value is `@uknown default` when switching over
the enumeration to account for that. C++ follows a similar principle,
by exposing an `unknown_default` case that can then be matched in a switch.

For example, given the following resilient enumeration:

```swift
// Swift module 'DateTime'
enum DateFormatStyle {
case medium
case full
}
```

In C++, you need do an exhaustive switch over all cases and the unknown default
case to avoid any compiler warnings:

```c++
using namespace DateTime;
void test(const DateFormatStyle &style) {
switch (style) {
case DateFormatStyle::medium:
...
break;
case DateFormatStyle::full:
...
break;
case DateFormatStyle::unknown_default: // just like Swift's @unknown default
// Some case value added in a future version of enum.
break;
}
}
```

The `unknown_default` case value is not a constructible case and you will get a compiler error if you try to construct it in C++.

## Using Swift Class Types

Swift class types that are usable from C++ are available in their corresponding module namespace. They’re bridged over as a C++ class that stores a referenced counted pointer inside of it. Its initializers, methods and properties are exposed as members of the C++ class.
Expand Down