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::kFooin C++ isMyEnum::kFooin Rust. - The enum can be converted to and from its underlying type using
FromandInto. For example,static_cast<int32_t>(x)isi32::from(x)in Rust, and vice versastatic_cast<MyEnum>(x)isMyEnum::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
FromandInto, notas. - 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.