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

Rust bindings for C++ enums

A C++ enum is mapped to a Rust struct with a similar API to a Rust enum.

  • The enumerated constants are present as associated constants: MyEnum::kFoo in C++ is MyEnum::kFoo in Rust.
  • The enum can be converted to and from its underlying type using From and Into. For example, static_cast<int32_t>(x) is i32::from(x) in Rust, and vice versa static_cast<MyEnum>(x) is MyEnum::from(x).

However, a C++ enum is not a Rust enum. Some features of Rust enums are not supported:

  • C++ enums must be converted using From and Into, not as.
  • C++ enums do not have exhaustive pattern matching.

Example

Given the following C++ header:

// 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

#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_CPP_ENUM_EXAMPLE_H_
#define THIRD_PARTY_CRUBIT_EXAMPLES_CPP_ENUM_EXAMPLE_H_

enum Color {
  kRed,
  kBlue,
  kGreen,
};

#endif  // THIRD_PARTY_CRUBIT_EXAMPLES_CPP_ENUM_EXAMPLE_H_

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 Rust bindings for the following C++ target:
// //examples/cpp/enum:example_lib
// Features: custom_ffi_types, non_unpin_ctor, std_unique_ptr, std_vector, supported

#![rustfmt::skip]
#![feature(allocator_api, cfg_sanitize, custom_inner_attributes, register_tool)]
#![allow(stable_features)]
#![no_std]
#![allow(improper_ctypes)]
#![allow(nonstandard_style)]
#![allow(dead_code, unused_mut)]
#![deny(warnings)]

#[repr(transparent)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, PartialOrd, Ord)]
///CRUBIT_ANNOTATE: cpp_type=Color
pub struct Color(::ffi_11::c_uint);
impl Color {
    pub const kRed: Color = Color(0);
    pub const kBlue: Color = Color(1);
    pub const kGreen: Color = Color(2);
}
impl From<::ffi_11::c_uint> for Color {
    fn from(value: ::ffi_11::c_uint) -> Color {
        Color(value)
    }
}
impl From<Color> for ::ffi_11::c_uint {
    fn from(value: Color) -> ::ffi_11::c_uint {
        value.0
    }
}

Why isn’t it an enum?

A C++ enum cannot be translated directly to a Rust enum, because C++ enums are “representationally non-exhaustive”: a C++ enum can have any value supported by the underlying type, even one not listed in the enumerators. For example, in the enum above, static_cast<Color>(42) is a valid instance of Color, even though none of kRed, kBlue, or kGreen have that value.

Rust enums, in contrast, are representationally exhaustive. An enum declares a closed set of valid discriminants, and it is undefined behavior to attempt to create an enum with a value outside of that set, whether it’s via transmute, a raw pointer cast, or Crubit. The behavior is undefined the moment the invalid value is created, even if it is never used.

Since a value like static_cast<Color>(42) is not in the list of enumerators, a Rust enum cannot be used to represent an arbitrary C++ enum. Instead, the Rust bindings are a struct. This struct is given the most natural and enum-like API possible, though there are still gaps. (Casts using as, for example, will not work with a C++ enum.)

What about #[non_exhaustive]?

The #[non_exhaustive] attribute on an enum communicates to external crates that more variants may be added in the future, and so a match requires a wildcard branch. Within the defining crate, non_exhaustive has no effect. It remains undefined behavior to transmute from integers not declared by the enum.