Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C++ bindings for Rust enums

A Rust enum is mapped to an opaque C++ type.

To receive C++ bindings, the enum must be movable in C++. See Movable Types.

Example

Given the following Rust crate:

// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#[derive(Copy, Clone, Default)]
pub enum Color {
    #[default]
    Red,
    Blue,
    Green,
}

Crubit will generate the following bindings:

// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

// Automatically @generated C++ bindings for the following Rust crate:
// example_crate_golden
// Features: supported, types

// clang-format off
#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_RUST_ENUM_EXAMPLE_CRATE_GOLDEN
#define THIRD_PARTY_CRUBIT_EXAMPLES_RUST_ENUM_EXAMPLE_CRATE_GOLDEN

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
#pragma clang diagnostic ignored "-Wunused-private-field"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include "support/annotations_internal.h"
#include "support/internal/slot.h"

#include <array>
#include <cstddef>
#include <cstring>
#include <type_traits>

namespace example_crate {

// Generated from: examples/rust/enum/example.rs;l=6
struct CRUBIT_INTERNAL_RUST_TYPE(":: example_crate_golden :: Color") alignas(1)
    [[clang::trivial_abi]] Color final {
 public:
  // Default::default
  Color();

  // Generated from:
  // examples/rust/enum/example.rs;l=8
  static constexpr Color MakeRed();

  // Generated from:
  // examples/rust/enum/example.rs;l=9
  static constexpr Color MakeBlue();

  // Generated from:
  // examples/rust/enum/example.rs;l=10
  static constexpr Color MakeGreen();

  // No custom `Drop` impl and no custom "drop glue" required
  ~Color() = default;
  Color(Color&&) = default;
  ::example_crate::Color& operator=(Color&&) = default;

  // Rust types that are `Copy` get trivial, `default` C++ copy constructor and
  // assignment operator.
  Color(const Color&) = default;
  ::example_crate::Color& operator=(const Color&) = default;
  Color(::crubit::UnsafeRelocateTag, Color&& value) {
    std::memcpy(this, &value, sizeof(value));
  }

 private:
  // Field type has been replaced with a blob of bytes: No support for bindings
  // of individual non-repr(C) `enum`s
  std::array<unsigned char, 1> __opaque_blob_of_bytes;

 private:
  struct PrivateBytesTag {};
  constexpr Color(PrivateBytesTag, std::array<unsigned char, 1> bytes)
      : __opaque_blob_of_bytes(bytes) {}

 private:
  static void __crubit_field_offset_assertions();
};

static_assert(
    sizeof(Color) == 1,
    "Verify that ADT layout didn't change since this header got generated");
static_assert(
    alignof(Color) == 1,
    "Verify that ADT layout didn't change since this header got generated");
namespace __crubit_internal {
extern "C" void __crubit_thunk_default(::example_crate::Color* __ret_ptr);
}
inline ::example_crate::Color::Color() {
  __crubit_internal::__crubit_thunk_default(this);
}
// `static` constructor
inline constexpr Color Color::MakeRed() {
  return Color(PrivateBytesTag{}, {0});
}

// `static` constructor
inline constexpr Color Color::MakeBlue() {
  return Color(PrivateBytesTag{}, {1});
}

// `static` constructor
inline constexpr Color Color::MakeGreen() {
  return Color(PrivateBytesTag{}, {2});
}
static_assert(std::is_trivially_destructible_v<Color>);
static_assert(std::is_trivially_move_constructible_v<::example_crate::Color>);
static_assert(std::is_trivially_move_assignable_v<::example_crate::Color>);
static_assert(std::is_trivially_copy_constructible_v<::example_crate::Color>);
static_assert(std::is_trivially_copy_assignable_v<::example_crate::Color>);
inline void Color::__crubit_field_offset_assertions() {
  static_assert(0 == offsetof(Color, __opaque_blob_of_bytes));
}
}  // namespace example_crate

#pragma clang diagnostic pop
#endif  // THIRD_PARTY_CRUBIT_EXAMPLES_RUST_ENUM_EXAMPLE_CRATE_GOLDEN

Why isn’t it a C++ enum?

A repr(i32) or fieldless repr(C) enum is very similar to a C++ enum. However, Rust enums are exhaustive: any value not explicitly listed in the enum declaration does not exist, and it is undefined behavior to attempt to create one.

C++ enums, in contrast, are “non-exhaustive”: a C++ enum can have any value supported by the underlying type, even one not listed in the enumerators. For example, if the above example were a C++ enum, static_cast<Color>(42) would be a valid instance of Color, even though neither Red, Blue, nor Green have that value.

In order to prevent invalid Rust values from being produced by C++, a C++ enum cannot be used to represent a Rust enum. Instead, the C++ bindings are a struct, even for fieldless enums.

C++ movable

To receive C++ bindings, the enum must be movable in C++. See Movable Types.

Enums with payload

Each variant of a Rust enum may contain an additional payload (a tuple or a struct). C++ bindings for Rust enums provide the following ways of working with an enum payload:

  • Constructing an enum variant with the given payload by calling a static Make<variant name> method (one such method is injected for each of enum variants). The following bugs track future work in this area:

    • b/487357254: Constructing variants with a struct payload
    • b/489085607: Bindings for constructing enums should be constexpr
  • TODO(b/262737383): Matching enum variants and inspecting their payload.

Example

Given the following Rust crate:

// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#[derive(Copy, Clone)]
pub enum Color {
    /// A completely transparent color (no payload)
    Transparent,
    /// A grayscale value from 0 to 255
    Grayscale(u8),
    /// Red, Green, and Blue values from 0 to 255
    Rgb(u8, u8, u8),
}

Crubit will generate the following bindings:

// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

// Automatically @generated C++ bindings for the following Rust crate:
// example_crate_golden
// Features: supported, types

// clang-format off
#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_RUST_ENUM_WITH_PAYLOAD_EXAMPLE_CRATE_GOLDEN
#define THIRD_PARTY_CRUBIT_EXAMPLES_RUST_ENUM_WITH_PAYLOAD_EXAMPLE_CRATE_GOLDEN

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
#pragma clang diagnostic ignored "-Wunused-private-field"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include "support/annotations_internal.h"
#include "support/internal/slot.h"

#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <utility>

namespace example_crate {

// Generated from:
// examples/rust/enum_with_payload/example.rs;l=6
struct CRUBIT_INTERNAL_RUST_TYPE(":: example_crate_golden :: Color") alignas(1)
    [[clang::trivial_abi]] Color final {
 public:
  // `example_crate_golden::Color` doesn't implement the `Default` trait
  Color() = delete;

  //  A completely transparent color (no payload)
  //
  // Generated from:
  // examples/rust/enum_with_payload/example.rs;l=8
  static constexpr Color MakeTransparent();

  //  A grayscale value from 0 to 255
  //
  // Generated from:
  // examples/rust/enum_with_payload/example.rs;l=10
  static ::example_crate::Color MakeGrayscale(std::uint8_t __param_0);

  //  Red, Green, and Blue values from 0 to 255
  //
  // Generated from:
  // examples/rust/enum_with_payload/example.rs;l=12
  static ::example_crate::Color MakeRgb(std::uint8_t __param_0,
                                        std::uint8_t __param_1,
                                        std::uint8_t __param_2);

  // No custom `Drop` impl and no custom "drop glue" required
  ~Color() = default;
  Color(Color&&) = default;
  ::example_crate::Color& operator=(Color&&) = default;

  // Rust types that are `Copy` get trivial, `default` C++ copy constructor and
  // assignment operator.
  Color(const Color&) = default;
  ::example_crate::Color& operator=(const Color&) = default;
  Color(::crubit::UnsafeRelocateTag, Color&& value) {
    std::memcpy(this, &value, sizeof(value));
  }

 private:
  // Field type has been replaced with a blob of bytes: No support for bindings
  // of individual non-repr(C) `enum`s
  std::array<unsigned char, 4> __opaque_blob_of_bytes;

 private:
  struct PrivateBytesTag {};
  constexpr Color(PrivateBytesTag, std::array<unsigned char, 4> bytes)
      : __opaque_blob_of_bytes(bytes) {}

 private:
  static void __crubit_field_offset_assertions();
};

static_assert(
    sizeof(Color) == 4,
    "Verify that ADT layout didn't change since this header got generated");
static_assert(
    alignof(Color) == 1,
    "Verify that ADT layout didn't change since this header got generated");

// `static` constructor
inline constexpr Color Color::MakeTransparent() {
  return Color(PrivateBytesTag{}, {0, 0, 0, 0});
}

namespace __crubit_internal {
extern "C" void __crubit_thunk_Grayscale(std::uint8_t,
                                         ::example_crate::Color* __ret_ptr);
}
inline ::example_crate::Color Color::MakeGrayscale(std::uint8_t __param_0) {
  crubit::Slot<::example_crate::Color> __return_value_ret_val_holder;
  auto* __return_value_storage = __return_value_ret_val_holder.Get();
  __crubit_internal::__crubit_thunk_Grayscale(__param_0,
                                              __return_value_storage);
  return std::move(__return_value_ret_val_holder).AssumeInitAndTakeValue();
}

namespace __crubit_internal {
extern "C" void __crubit_thunk_Rgb(std::uint8_t, std::uint8_t, std::uint8_t,
                                   ::example_crate::Color* __ret_ptr);
}
inline ::example_crate::Color Color::MakeRgb(std::uint8_t __param_0,
                                             std::uint8_t __param_1,
                                             std::uint8_t __param_2) {
  crubit::Slot<::example_crate::Color> __return_value_ret_val_holder;
  auto* __return_value_storage = __return_value_ret_val_holder.Get();
  __crubit_internal::__crubit_thunk_Rgb(__param_0, __param_1, __param_2,
                                        __return_value_storage);
  return std::move(__return_value_ret_val_holder).AssumeInitAndTakeValue();
}
static_assert(std::is_trivially_destructible_v<Color>);
static_assert(std::is_trivially_move_constructible_v<::example_crate::Color>);
static_assert(std::is_trivially_move_assignable_v<::example_crate::Color>);
static_assert(std::is_trivially_copy_constructible_v<::example_crate::Color>);
static_assert(std::is_trivially_copy_assignable_v<::example_crate::Color>);
inline void Color::__crubit_field_offset_assertions() {
  static_assert(0 == offsetof(Color, __opaque_blob_of_bytes));
}
}  // namespace example_crate

#pragma clang diagnostic pop
#endif  // THIRD_PARTY_CRUBIT_EXAMPLES_RUST_ENUM_WITH_PAYLOAD_EXAMPLE_CRATE_GOLDEN