C++ bindings for Rust traits
Crubit allows C++ callers to invoke Rust trait implementations. For each trait SomeTrait, Crubit generates a matching C++ struct MyTrait. To call a trait
method SomeTrait::some_fn implemented by SomeType, C++ callers can use
SomeTrait::impl<SomeType>::some_fn(args...).
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
pub trait MyTrait {
fn add_with(&self, y: i32) -> i32;
fn describe(&self) -> &'static str;
}
#[derive(Clone, Copy, Default)]
pub struct MyStruct {
x: i32,
}
impl MyStruct {
pub fn new(x: i32) -> Self {
Self { x }
}
}
impl MyTrait for MyStruct {
fn add_with(&self, y: i32) -> i32 {
self.x + y
}
fn describe(&self) -> &'static str {
"MyStruct"
}
}
You can call the trait from C++ using the following code:
// 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
#include <cstdint>
#include <iostream>
#include "support/rs_std/str_ref.h"
#include "support/rs_std/traits.h"
// The generated bindings are in a header at the same path as the
// `example_crate` rust_library, with a `.h` suffix.
#include "examples/rust/trait/example_crate.h"
template <typename T>
requires(rs_std::where_v<T, example_crate::MyTrait>)
uint32_t add_with_2(T const& self) {
return example_crate::MyTrait::impl<T>::add_with(self, 2);
}
int main(int argc, char* argv[]) {
// The generated bindings are in a namespace with the same name as the
// target crate:
example_crate::MyStruct s = example_crate::MyStruct::new_(2);
uint32_t result =
example_crate::MyTrait::impl<example_crate::MyStruct>::add_with(s, 3);
rs_std::StrRef description =
example_crate::MyTrait::impl<example_crate::MyStruct>::describe(s);
std::cout << "Result: " << result
<< "\ndescription: " << description.to_string_view() << std::endl;
return 0;
}
Each trait has an associated impl member which is generic upon the Self type
of the trait implementation, and which provides all of the associated types,
consts, and functions.
Checking if a trait is implemented
C++ users can check if a trait implementation is available using
rs_std::where_v<T, SomeTrait>. This is useful when writing templated C++
functions, as it can be used with requires (or enable_if) to specify that a
particular trait is implemented by a type parameter. For example:
// 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
#include <cstdint>
#include <iostream>
#include "support/rs_std/str_ref.h"
#include "support/rs_std/traits.h"
// The generated bindings are in a header at the same path as the
// `example_crate` rust_library, with a `.h` suffix.
#include "examples/rust/trait/example_crate.h"
template <typename T>
requires(rs_std::where_v<T, example_crate::MyTrait>)
uint32_t add_with_2(T const& self) {
return example_crate::MyTrait::impl<T>::add_with(self, 2);
}
int main(int argc, char* argv[]) {
// The generated bindings are in a namespace with the same name as the
// target crate:
example_crate::MyStruct s = example_crate::MyStruct::new_(2);
uint32_t result =
example_crate::MyTrait::impl<example_crate::MyStruct>::add_with(s, 3);
rs_std::StrRef description =
example_crate::MyTrait::impl<example_crate::MyStruct>::describe(s);
std::cout << "Result: " << result
<< "\ndescription: " << description.to_string_view() << std::endl;
return 0;
}
Limitations
Some trait implementations will not receive bindings:
- Trait implementations with generic parameters (e.g.
impl<T> ...) will not receive bindings.- For example, an implementation
impl<U> TwoArgs<i32, U> for MyStructwill not receive bindings because theimplhas a type parameter (U). - This also means that blanket impls (e.g.
impl<T> Trait for T) are not supported.
- For example, an implementation
- Traits with
constparameters (e.g.trait T<const V: usize>) do not yet not receive bindings. - Trait methods will not receive bindings if their parameter or return types are not yet supported by Crubit.
- Standard library traits are not supported yet (b/483382648)
How does it work?
Under the hood, Crubit will generate three bindings from the example Rust crate.
A template specialization for impl MyTrait for MyStruct:
// 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: fmt, supported, types
// clang-format off
#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_RUST_TRAIT_EXAMPLE_CRATE_GOLDEN
#define THIRD_PARTY_CRUBIT_EXAMPLES_RUST_TRAIT_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"
#pragma clang diagnostic ignored "-Wignored-attributes"
#include "support/annotations_internal.h"
#include "support/internal/slot.h"
#include "support/rs_std/str_ref.h"
#include "support/rs_std/traits.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <utility>
namespace example_crate {
// Generated from:
// examples/rust/trait/example.rs;l=12
struct CRUBIT_INTERNAL_RUST_TYPE(":: example_crate_golden :: MyStruct") alignas(
4) [[clang::trivial_abi]] MyStruct final {
public:
// Default::default
MyStruct();
// No custom `Drop` impl and no custom "drop glue" required
~MyStruct() = default;
MyStruct(MyStruct&&) = default;
MyStruct& operator=(MyStruct&&) = default;
// Rust types that are `Copy` get trivial, `default` C++ copy constructor and
// assignment operator.
MyStruct(const MyStruct&) = default;
MyStruct& operator=(const MyStruct&) = default;
MyStruct(::crubit::UnsafeRelocateTag, MyStruct&& value) {
::std::memcpy(this, &value, sizeof(value));
}
// Generated from:
// examples/rust/trait/example.rs;l=17
static ::example_crate::MyStruct new_(::std::int32_t x);
private:
union {
// Generated from:
// examples/rust/trait/example.rs;l=13
::std::int32_t x;
};
private:
static void __crubit_field_offset_assertions();
};
// Generated from: examples/rust/trait/example.rs;l=5
struct CRUBIT_INTERNAL_RUST_TYPE(":: example_crate_golden :: MyTrait") MyTrait {
template <typename T>
using impl = rs_std::impl<T, MyTrait>;
};
} // namespace example_crate
template <>
struct rs_std::impl<::example_crate::MyStruct, ::example_crate::MyTrait> {
static constexpr bool kIsImplemented = true;
// Generated from:
// examples/rust/trait/example.rs;l=23
static ::std::int32_t add_with(::example_crate::MyStruct const& self,
::std::int32_t y);
// Generated from:
// examples/rust/trait/example.rs;l=27
static rs_std::StrRef describe(::example_crate::MyStruct const& self);
};
namespace example_crate {
static_assert(
sizeof(MyStruct) == 4,
"Verify that ADT layout didn't change since this header got generated");
static_assert(
alignof(MyStruct) == 4,
"Verify that ADT layout didn't change since this header got generated");
namespace __crubit_internal {
extern "C" void __crubit_thunk_default(::example_crate::MyStruct* __ret_ptr);
}
inline ::example_crate::MyStruct::MyStruct() {
__crubit_internal::__crubit_thunk_default(this);
}
static_assert(::std::is_trivially_destructible_v<MyStruct>);
static_assert(
::std::is_trivially_move_constructible_v<::example_crate::MyStruct>);
static_assert(::std::is_trivially_move_assignable_v<::example_crate::MyStruct>);
static_assert(
::std::is_trivially_copy_constructible_v<::example_crate::MyStruct>);
static_assert(::std::is_trivially_copy_assignable_v<::example_crate::MyStruct>);
namespace __crubit_internal {
extern "C" void __crubit_thunk_new(::std::int32_t,
::example_crate::MyStruct* __ret_ptr);
}
inline ::example_crate::MyStruct MyStruct::new_(::std::int32_t x) {
crubit::Slot<::example_crate::MyStruct> __return_value_ret_val_holder;
auto* __return_value_storage = __return_value_ret_val_holder.Get();
__crubit_internal::__crubit_thunk_new(x, __return_value_storage);
return ::std::move(__return_value_ret_val_holder).AssumeInitAndTakeValue();
}
inline void MyStruct::__crubit_field_offset_assertions() {
static_assert(0 == offsetof(MyStruct, x));
}
} // namespace example_crate
namespace example_crate {
namespace __crubit_internal {
extern "C" ::std::int32_t __crubit_thunk_MyTrait_uadd_uwith(
::example_crate::MyStruct const&, ::std::int32_t);
}
} // namespace example_crate
inline ::std::int32_t
rs_std::impl<::example_crate::MyStruct, ::example_crate::MyTrait>::add_with(
::example_crate::MyStruct const& self, ::std::int32_t y) {
return example_crate::__crubit_internal::__crubit_thunk_MyTrait_uadd_uwith(
self, y);
}
namespace example_crate {
namespace __crubit_internal {
extern "C" rs_std::StrRef __crubit_thunk_MyTrait_udescribe(
::example_crate::MyStruct const&);
}
} // namespace example_crate
inline rs_std::StrRef
rs_std::impl<::example_crate::MyStruct, ::example_crate::MyTrait>::describe(
::example_crate::MyStruct const& self) {
return example_crate::__crubit_internal::__crubit_thunk_MyTrait_udescribe(
self);
}
#pragma clang diagnostic pop
#endif // THIRD_PARTY_CRUBIT_EXAMPLES_RUST_TRAIT_EXAMPLE_CRATE_GOLDEN
Our generated rs_std::impl specialization is an implementation detail of
binding generation. It holds the actual thunks that call into rust to implement
our trait, but they should be accessed through our template struct MyTrait:
// 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: fmt, supported, types
// clang-format off
#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_RUST_TRAIT_EXAMPLE_CRATE_GOLDEN
#define THIRD_PARTY_CRUBIT_EXAMPLES_RUST_TRAIT_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"
#pragma clang diagnostic ignored "-Wignored-attributes"
#include "support/annotations_internal.h"
#include "support/internal/slot.h"
#include "support/rs_std/str_ref.h"
#include "support/rs_std/traits.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <utility>
namespace example_crate {
// Generated from:
// examples/rust/trait/example.rs;l=12
struct CRUBIT_INTERNAL_RUST_TYPE(":: example_crate_golden :: MyStruct") alignas(
4) [[clang::trivial_abi]] MyStruct final {
public:
// Default::default
MyStruct();
// No custom `Drop` impl and no custom "drop glue" required
~MyStruct() = default;
MyStruct(MyStruct&&) = default;
MyStruct& operator=(MyStruct&&) = default;
// Rust types that are `Copy` get trivial, `default` C++ copy constructor and
// assignment operator.
MyStruct(const MyStruct&) = default;
MyStruct& operator=(const MyStruct&) = default;
MyStruct(::crubit::UnsafeRelocateTag, MyStruct&& value) {
::std::memcpy(this, &value, sizeof(value));
}
// Generated from:
// examples/rust/trait/example.rs;l=17
static ::example_crate::MyStruct new_(::std::int32_t x);
private:
union {
// Generated from:
// examples/rust/trait/example.rs;l=13
::std::int32_t x;
};
private:
static void __crubit_field_offset_assertions();
};
// Generated from: examples/rust/trait/example.rs;l=5
struct CRUBIT_INTERNAL_RUST_TYPE(":: example_crate_golden :: MyTrait") MyTrait {
template <typename T>
using impl = rs_std::impl<T, MyTrait>;
};
} // namespace example_crate
template <>
struct rs_std::impl<::example_crate::MyStruct, ::example_crate::MyTrait> {
static constexpr bool kIsImplemented = true;
// Generated from:
// examples/rust/trait/example.rs;l=23
static ::std::int32_t add_with(::example_crate::MyStruct const& self,
::std::int32_t y);
// Generated from:
// examples/rust/trait/example.rs;l=27
static rs_std::StrRef describe(::example_crate::MyStruct const& self);
};
namespace example_crate {
static_assert(
sizeof(MyStruct) == 4,
"Verify that ADT layout didn't change since this header got generated");
static_assert(
alignof(MyStruct) == 4,
"Verify that ADT layout didn't change since this header got generated");
namespace __crubit_internal {
extern "C" void __crubit_thunk_default(::example_crate::MyStruct* __ret_ptr);
}
inline ::example_crate::MyStruct::MyStruct() {
__crubit_internal::__crubit_thunk_default(this);
}
static_assert(::std::is_trivially_destructible_v<MyStruct>);
static_assert(
::std::is_trivially_move_constructible_v<::example_crate::MyStruct>);
static_assert(::std::is_trivially_move_assignable_v<::example_crate::MyStruct>);
static_assert(
::std::is_trivially_copy_constructible_v<::example_crate::MyStruct>);
static_assert(::std::is_trivially_copy_assignable_v<::example_crate::MyStruct>);
namespace __crubit_internal {
extern "C" void __crubit_thunk_new(::std::int32_t,
::example_crate::MyStruct* __ret_ptr);
}
inline ::example_crate::MyStruct MyStruct::new_(::std::int32_t x) {
crubit::Slot<::example_crate::MyStruct> __return_value_ret_val_holder;
auto* __return_value_storage = __return_value_ret_val_holder.Get();
__crubit_internal::__crubit_thunk_new(x, __return_value_storage);
return ::std::move(__return_value_ret_val_holder).AssumeInitAndTakeValue();
}
inline void MyStruct::__crubit_field_offset_assertions() {
static_assert(0 == offsetof(MyStruct, x));
}
} // namespace example_crate
namespace example_crate {
namespace __crubit_internal {
extern "C" ::std::int32_t __crubit_thunk_MyTrait_uadd_uwith(
::example_crate::MyStruct const&, ::std::int32_t);
}
} // namespace example_crate
inline ::std::int32_t
rs_std::impl<::example_crate::MyStruct, ::example_crate::MyTrait>::add_with(
::example_crate::MyStruct const& self, ::std::int32_t y) {
return example_crate::__crubit_internal::__crubit_thunk_MyTrait_uadd_uwith(
self, y);
}
namespace example_crate {
namespace __crubit_internal {
extern "C" rs_std::StrRef __crubit_thunk_MyTrait_udescribe(
::example_crate::MyStruct const&);
}
} // namespace example_crate
inline rs_std::StrRef
rs_std::impl<::example_crate::MyStruct, ::example_crate::MyTrait>::describe(
::example_crate::MyStruct const& self) {
return example_crate::__crubit_internal::__crubit_thunk_MyTrait_udescribe(
self);
}
#pragma clang diagnostic pop
#endif // THIRD_PARTY_CRUBIT_EXAMPLES_RUST_TRAIT_EXAMPLE_CRATE_GOLDEN
Each struct generated for a Rust trait has the impl member that provides
access to the generated rs_std::impl. This is the preferred way to reference
the generated rs_std::impl.
Finally, a struct for MyStruct:
// 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: fmt, supported, types
// clang-format off
#ifndef THIRD_PARTY_CRUBIT_EXAMPLES_RUST_TRAIT_EXAMPLE_CRATE_GOLDEN
#define THIRD_PARTY_CRUBIT_EXAMPLES_RUST_TRAIT_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"
#pragma clang diagnostic ignored "-Wignored-attributes"
#include "support/annotations_internal.h"
#include "support/internal/slot.h"
#include "support/rs_std/str_ref.h"
#include "support/rs_std/traits.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <utility>
namespace example_crate {
// Generated from:
// examples/rust/trait/example.rs;l=12
struct CRUBIT_INTERNAL_RUST_TYPE(":: example_crate_golden :: MyStruct") alignas(
4) [[clang::trivial_abi]] MyStruct final {
public:
// Default::default
MyStruct();
// No custom `Drop` impl and no custom "drop glue" required
~MyStruct() = default;
MyStruct(MyStruct&&) = default;
MyStruct& operator=(MyStruct&&) = default;
// Rust types that are `Copy` get trivial, `default` C++ copy constructor and
// assignment operator.
MyStruct(const MyStruct&) = default;
MyStruct& operator=(const MyStruct&) = default;
MyStruct(::crubit::UnsafeRelocateTag, MyStruct&& value) {
::std::memcpy(this, &value, sizeof(value));
}
// Generated from:
// examples/rust/trait/example.rs;l=17
static ::example_crate::MyStruct new_(::std::int32_t x);
private:
union {
// Generated from:
// examples/rust/trait/example.rs;l=13
::std::int32_t x;
};
private:
static void __crubit_field_offset_assertions();
};
// Generated from: examples/rust/trait/example.rs;l=5
struct CRUBIT_INTERNAL_RUST_TYPE(":: example_crate_golden :: MyTrait") MyTrait {
template <typename T>
using impl = rs_std::impl<T, MyTrait>;
};
} // namespace example_crate
template <>
struct rs_std::impl<::example_crate::MyStruct, ::example_crate::MyTrait> {
static constexpr bool kIsImplemented = true;
// Generated from:
// examples/rust/trait/example.rs;l=23
static ::std::int32_t add_with(::example_crate::MyStruct const& self,
::std::int32_t y);
// Generated from:
// examples/rust/trait/example.rs;l=27
static rs_std::StrRef describe(::example_crate::MyStruct const& self);
};
namespace example_crate {
static_assert(
sizeof(MyStruct) == 4,
"Verify that ADT layout didn't change since this header got generated");
static_assert(
alignof(MyStruct) == 4,
"Verify that ADT layout didn't change since this header got generated");
namespace __crubit_internal {
extern "C" void __crubit_thunk_default(::example_crate::MyStruct* __ret_ptr);
}
inline ::example_crate::MyStruct::MyStruct() {
__crubit_internal::__crubit_thunk_default(this);
}
static_assert(::std::is_trivially_destructible_v<MyStruct>);
static_assert(
::std::is_trivially_move_constructible_v<::example_crate::MyStruct>);
static_assert(::std::is_trivially_move_assignable_v<::example_crate::MyStruct>);
static_assert(
::std::is_trivially_copy_constructible_v<::example_crate::MyStruct>);
static_assert(::std::is_trivially_copy_assignable_v<::example_crate::MyStruct>);
namespace __crubit_internal {
extern "C" void __crubit_thunk_new(::std::int32_t,
::example_crate::MyStruct* __ret_ptr);
}
inline ::example_crate::MyStruct MyStruct::new_(::std::int32_t x) {
crubit::Slot<::example_crate::MyStruct> __return_value_ret_val_holder;
auto* __return_value_storage = __return_value_ret_val_holder.Get();
__crubit_internal::__crubit_thunk_new(x, __return_value_storage);
return ::std::move(__return_value_ret_val_holder).AssumeInitAndTakeValue();
}
inline void MyStruct::__crubit_field_offset_assertions() {
static_assert(0 == offsetof(MyStruct, x));
}
} // namespace example_crate
namespace example_crate {
namespace __crubit_internal {
extern "C" ::std::int32_t __crubit_thunk_MyTrait_uadd_uwith(
::example_crate::MyStruct const&, ::std::int32_t);
}
} // namespace example_crate
inline ::std::int32_t
rs_std::impl<::example_crate::MyStruct, ::example_crate::MyTrait>::add_with(
::example_crate::MyStruct const& self, ::std::int32_t y) {
return example_crate::__crubit_internal::__crubit_thunk_MyTrait_uadd_uwith(
self, y);
}
namespace example_crate {
namespace __crubit_internal {
extern "C" rs_std::StrRef __crubit_thunk_MyTrait_udescribe(
::example_crate::MyStruct const&);
}
} // namespace example_crate
inline rs_std::StrRef
rs_std::impl<::example_crate::MyStruct, ::example_crate::MyTrait>::describe(
::example_crate::MyStruct const& self) {
return example_crate::__crubit_internal::__crubit_thunk_MyTrait_udescribe(
self);
}
#pragma clang diagnostic pop
#endif // THIRD_PARTY_CRUBIT_EXAMPLES_RUST_TRAIT_EXAMPLE_CRATE_GOLDEN
Our struct is generated normally and exists purely so we have something to implement our trait for.