cxx_enumext

Overview
cxx-enumext
is a Rust crate that extends the cxx
library to provide
a mapping between Rust variant enums and c++ standards like std::variant
, std::optional
and std::expected
. Unfortunately the memory modal and null pointer optimisations in play
make direct mappings between the two extremely difficult. As such, cxx-enumext
provides
analogs which should be identical in usage.
rust::enm::variant
is a analog for std::variant
which can fully map a #[repr(c)]
Rust
Enum. The #[cxx_enumext::extern_type]
macro will transform yur enums apropreatly as well as
provide some sanity check static assertions about their contents for the current limitations of cxx
ffi.
rust::enum::optional
is a derivation from rust::enm::variant
which provides the interface of
std::optional
rust::enum::expected
is a derivation from rust::enm::variant
which provides the interface of
std::expected
Quick tutorial
To use cxx-enumext
, first start by adding cxx
to your project. Then add the following to your
Cargo.toml
:
[dependencies]
cxx-enumext = "0.1"
If your going to put other extern(d) types or shared types inside your enums declare them
in a seperate module so that you don't deal with the c++ compiler complaing
about incompleate types
#[cxx::bridge]
pub mod ffi {
#[derive(Debug)]
struct SharedData {
size: i64,
tags: Vec<String>,
}
extern "Rust" {
type RustValue;
fn read(&self) -> &String;
fn new_rust_value() -> Box<RustValue>;
}
}
fn new_rust_value() -> Box<RustValue> {
Box::new(RustValue {
value: "A Hidden Rust String".into(),
})
}
#[derive(Default, Debug, Clone)]
pub struct RustValue {
value: String,
}
impl RustValue {
pub fn read(&self) -> &String {
&self.value
}
pub fn new(val: &str) -> Self {
RustValue {
value: val.to_string(),
}
}
}
Now, simply declare your enum, optional, or expected
use data::{RustValue, ffi::SharedData};
#[derive(Debug)]
#[cxx_enumext::extern_type]
pub enum RustEnum<'a> {
Empty,
Num(i64),
String(String),
Bool(bool),
Shared(SharedData),
SharedRef(&'a SharedData),
Opaque(Box<RustValue>),
OpaqueRef(&'a RustValue),
Tuple(i32, i32),
Struct { val: i32, str: String },
Unit1,
Unit2,
}
#[cxx_enumext::extern_type(cxx_name = "OptionalInt32")]
#[derive(Debug)]
pub type OptionalI32 = Optional<i32>;
#[cxx_enumext::extern_type]
#[derive(Debug)]
pub type I32StringResult = cxx_enumext::Expected<i32, String>;
#[cxx::bridge]
pub mod ffi {
unsafe extern "C++" {
include!("my_crate/src/enum.h");
type RustEnum<'a> = super::RustEnum<'a>;
type I32StringResult = super::I32StringResult;
type OptionalInt32 = super::OptionalI32;
}
}
This will generate the cxx::ExternType
impl as well as some non-exhaustive
static assertions to check that contained types are at least externable.
For Optional
and Expected
types some std::convert::From<T>
impls will be generated
to convert from and to Option
and Result
types.
Now, in your C++ file, make sure to #include
the right headers:
#include "rust/cxx.h"
#include "rust/cxx_enumext.h"
#include "rust/cxx_enumext_macros.h"
And add a call to the CXX_DEFINE_VARIANT
, CXXASYNC_DEFINE_OPTIONAL
,
and or CXXASYNC_DEFINE_EXPECTED
macro as apropreate to define the C++ side of the types:
#include "rust/cxx.h"
#include "rust/cxx_enumext.h"
#include "rust/cxx_enumext_macros.h"
#include "my_crate/src/data.rs.h"
CXX_DEFINE_VARIANT(
RustEnum,
(UNIT(Empty) , TYPE(Num, int64_t) ,
TYPE(String, rust::string) ,
TYPE(Bool, bool) ,
TYPE(Shared, SharedData) ,
TYPE(
SharedRef,
std::reference_wrapper<SharedData>)
,
TYPE(Opaque, rust::box<RustValue>) ,
TYPE(OpaqueRef,
std::reference_wrapper<RustValue>) ,
TUPLE(Tuple, int32_t, int32_t)
,
STRUCT(Struct, int32_t val;
rust::string str;)
,
UNIT(Unit1)
,
UNIT(Unit2)
),
)
CXX_DEFINE_OPTIONAL(OptionalInt32, int32_t)
CXX_DEFINE_EXPECTED(I32StringResult, int32_t, rust::string)
You're all set! Now you can use the enum types on either side of your ffi.
Installation notes
You will need a C++ compiler that implements c++17
Refrence
rust::enm::variant
Simplicited declaration
namespace rust {
namespace enm {
template <std::size_t I, typename... Ts>
constexpr decltype(auto) get(variant_base<Ts...> &);
template <std::size_t I, typename... Ts>
constexpr decltype(auto) get(const variant_base<Ts...> &);
template <std::size_t I, typename... Ts>
constexpr std::add_pointer_t<variant_alternative_t<I, Ts...>>
get_if(variant_base<Ts...> *variant);
template <std::size_t I, typename... Ts>
constexpr const std::add_pointer_t<variant_alternative_t<I, Ts...>>
get_if(const variant_base<Ts...> *variant);
template <typename Visitor, typename... Ts>
constexpr decltype(auto) visit(Visitor &&visitor, variant_base<Ts...> &);
template <typename Visitor, typename... Ts>
constexpr decltype(auto) visit(Visitor &&visitor, const variant_base<Ts...> &);
template <std::size_t I, typename... Ts>
constexpr bool holds_alternative(const variant_base<Ts...> &variant);
template <typename T, typename... Ts,
typename = std::enable_if_t<
exactly_once<std::is_same_v<Ts, std::decay_t<T>>...>::value>>
constexpr bool holds_alternative(const variant_base<Ts...> &variant);
template <typename... Ts> struct variant {
template <typename T, typename D = std::decay_t<T>,
typename = std::enable_if_t<is_unique_v<T> &&
std::is_constructible_v<D, T>>>
variant(T &&other) noexcept(std::is_nothrow_constructible_v<D, T>);
template <typename T, typename... Args,
typename = std::enable_if_t<is_unique_v<T>>,
typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
explicit variant(std::in_place_type_t<T> type, Args &&...args) noexcept(
std::is_nothrow_constructible_v<T, Args...>);
template <std::size_t I, typename... Args, typename T = type_from_index_t<I>,
typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
explicit variant(
[[maybe_unused]] std::in_place_index_t<I> index,
Args &&...args) noexcept(std::is_nothrow_constructible_v<T, Args...>);
template <typename... Rs, typename = std::enable_if_t<
all_same_v<Rs...> && all_copy_constructible_v>>
variant(const std::variant<Rs...> &other);
template <typename... Rs, typename = std::enable_if_t<
all_same_v<Rs...> && all_move_constructible_v>>
variant(std::variant<Rs...> &&other);
variant_base &operator=(const variant_base &other);
variant_base &operator=(variant_base &&other) = delete;
template <typename T, typename = std::enable_if_t<
is_unique_v<T> && std::is_constructible_v<T &&, T>>>
variant_base &operator=(T &&other);
template <typename... Rs, typename = std::enable_if_t<
all_same_v<Rs...> && all_copy_constructible_v>>
variant_base &operator=(const std::variant<Rs...> &other);
template <typename... Rs, typename = std::enable_if_t<
all_same_v<Rs...> && all_move_constructible_v>>
variant_base &operator=(std::variant<Rs...> &&other);
template <typename T, typename... Args,
typename = std::enable_if_t<is_unique_v<T>>,
typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
T &emplace(Args &&...args);
template <std::size_t I, typename... Args, typename T = type_from_index_t<I>,
typename = std::enable_if_t<std::is_constructible_v<T, Args...>>>
T &emplace(Args &&...args);
constexpr std::size_t index() const noexcept;
void swap(variant_base &other);
struct bad_rust_variant_access : std::runtime_error {
bad_rust_variant_access(std::size_t index)
: std::runtime_error{"The index should be " + std::to_string(index)} {}
};
}
} }
rust::enm::Optional
Simplicited declaration
namespace rust {
namespace enm {
struct bad_rust_optional_access : std::runtime_error {
bad_rust_optional_access() : std::runtime_error{"Optional has no value"} {}
};
template <typename T> struct optional : public variant<monostate, T> {
using base = variant<monostate, T>;
optional() : base(monostate{}) {};
optional(std::nullopt_t) : base(monostate{}) {};
optional(const optional &) = default;
optional(optional &&) = delete;
using base::base;
using base::operator=;
using Some = T;
using None = monostate;
constexpr explicit operator bool() { return this->m_Index == 1; }
constexpr bool has_value() const noexcept { return this->m_Index == 1; }
constexpr bool is_some() const noexcept { return this->m_Index == 1; }
constexpr bool is_none() const noexcept { return this->m_Index == 0; }
constexpr T &value() &;
constexpr const T &value() const &;
constexpr void reset() noexcept;
constexpr const T *operator->() const noexcept;
constexpr T *operator->() noexcept;
constexpr const T &operator*() const & noexcept;
constexpr T &operator*() & noexcept;
template <class U = std::remove_cv_t<T>>
constexpr T value_or(U &&default_value) const &;
template <class F> constexpr auto and_then(F &&f) &;
template <class F> constexpr auto and_then(F &&f) const &;
template <class F> constexpr auto transform(F &&f) &;
template <class F> constexpr auto transform(F &&f) const &;
template <class F> constexpr T &or_else(F &&f) &;
template <class F> constexpr const T &or_else(F &&f) const &;
};
template <class T, class U>
inline constexpr bool operator==(const optional<T> &lhs,
const optional<U> &rhs);
template <class T, class U>
inline constexpr bool operator!=(const optional<T> &lhs,
const optional<U> &rhs);
template <class T, class U>
inline constexpr bool operator<(const optional<T> &lhs,
const optional<U> &rhs)
rust::enm::expected
Simplicited declaration
namespace rust {
namespace enm {
template <typename E> struct bad_rust_expected_access : std::runtime_error {
bad_rust_expected_access(const E &err)
: std::runtime_error{"Expected is the unexpected value"}, error(err) {}
E error;
};
template <typename T, typename E> struct expected : public variant<T, E> {
using base = variant<T, E>;
expected() = delete;
expected(const expected &) = default;
expected(expected &&) = delete;
using base::base;
using base::operator=;
using Ok = T;
using Err = E;
constexpr explicit operator bool() { return this->m_Index == 0; }
constexpr bool has_value() const noexcept { return this->m_Index == 0; }
constexpr T &value() &;
constexpr const T &value() const &;
constexpr E &error() &;
constexpr const E &error() const &;
constexpr const T *operator->() const noexcept;
constexpr T *operator->() noexcept;
constexpr const T &operator*() const & noexcept;
constexpr T &operator*() & noexcept;
template <class U = std::remove_cv_t<T>>
constexpr T value_or(U &&default_value) const &;
template <class G = E> constexpr E error_or(G &&default_value) const &;
template <class F> constexpr auto and_then(F &&f) &;
template <class F> constexpr auto and_then(F &&f) const &;
template <class F> constexpr auto transform(F &&f) &;
template <class F> constexpr auto transform(F &&f) const &;
template <class F> constexpr T &or_else(F &&f) &;
template <class F> constexpr const T &or_else(F &&f) const &;
template <class F> constexpr auto transform_error(F &&f) &;
template <class F> constexpr auto transform_error(F &&f) const &;
};
} }
Code of conduct
cxx-enumext
follows the same Code of Conduct as Rust itself. Reports can be made to the crate
authors.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in
this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without
any additional terms or conditions.