Bridging Swift Enums with Associated Values to C++ Conclusion Post

Hi everyone,

I’m excited to share the project I worked on this summer: Bridging Swift Enums with Associated Values to C++. This project aims to add basic support for using Swift enums (with or without associated values) from C++.

Functionality Preview

For the following Swift enum:

public enum E {
  case x(i: Double)
  case foobar
}

The generated C++ class will be (only showing enum-related public members):

class E final {
public:
  enum class cases {
    x,
    foobar
  };

  inline const static struct {  // impl struct for case x
    inline constexpr operator cases() const {
      return cases::x;
    }
    inline E operator()(double val) const;
  } x;
  inline bool isX() const;
  inline double getX() const;

  inline const static struct {  // impl struct for case foobar
    inline constexpr operator cases() const {
      return cases::foobar;
    }
    inline E operator()() const;
  } foobar;
  inline bool isFoobar() const;

  inline operator cases() const {
    switch (_getEnumTag()) {
      case 0: return cases::x;
      case 1: return cases::foobar;
      default: abort();
    }
  }
};

Each case in the Swift enum will be printed as a static member in the C++ class. The static members can be used as both cases for C++ switch and functions for enum creation. Each case will also have a corresponding is function. Cases with associated values will also have get functions to retrieve the values stored in the enum. The get function will trap if the enum is the wrong case.

If the case name is a C++ keyword, it will be printed with an extra underscore character.

If the enum is resilient, an extra static member named unknownDefault will be generated in the C++ class so that users can check if an enum is in this case, similar to Swift's @unknown default. Users cannot create an enum from unknownDefault.

Here's an example of using the implemented functionalities:

void switchEnum(const E &e) {
  switch (e) {
  case E::x:
    assert(e.isX());
    std::cout << e.getX() << '\n';
    break;
  case E::foobar:
    assert(e.isFoobar());
    break;
  }
}

void createEnum() {
  auto e1 = E::x(123);
  auto e3 = E::foobar();
}

Future Goals

Here are some functionalities that can be implemented in the future:

  • Support indirect enums;
  • Support enums with raw values;
  • Support using other types (e.g. classes, tuples, etc.) as types of associated values;
  • Allow exposing the enum and cases using different names in C++.

Thanks

Special thanks to Alex @Alex_L for providing this project, taking time reviewing all my pull requests, and answering my questions! I also want to thank Egor @egor.zhdan for invoking the CI test for me when Alex is on vacation, as well as Richard @rxwei and Luciano @LucianoPAlmeida for helping me to solve a build issue. Last but not least, I want to thank the Swift community for providing such a great opportunity to me.

Suggestions for Future Student Projects

At the end of the post, I would like to give some suggestions so that future student contributors can have an even better experience.

First, I think it may be better to recommend newcomers to use ninja, or Xcode with ninja if they are familiar with Xcode. Multiple forum posts say building with Xcode is not as stable as building with ninja. Therefore, in my opinion, asking newcomers to build with ninja will effectively reduce the frustration of build failures.

Second, I think it would be great if student contributors can be granted permission to invoke the Swift CI test themselves, probably after several successful merged pull requests. Student contributors don’t need to interrupt their mentors and the project can also progress smoother if they have such permission.

16 Likes