Generating Bindings to C++
bindgen
can handle some C++ features, but not all of them. To set
expectations: bindgen
will give you the type definitions and FFI declarations
you need to build an API to the C++ library, but using those types in Rust will
be nowhere near as nice as using them in C++. You will have to manually call
constructors, destructors, overloaded operators, etc yourself.
When passing in header files, the file will automatically be treated as C++ if
it ends in .hpp
. If it doesn't, adding -x c++
clang args can be used to
force C++ mode. You probably also want to use -std=c++14
or similar clang args
as well.
You pretty much must use allowlisting when working
with C++ to avoid pulling in all of the std::.*
types, many of which bindgen
cannot handle. Additionally, you may want to mark other types as
opaque that bindgen
stumbles on. It is recommended to mark
all of std::.*
opaque, and to allowlist only precisely the functions and types
you intend to use.
You should read up on the FAQs as well.
Supported Features
-
Inheritance (for the most part; there are some outstanding bugs)
-
Methods
-
Bindings to constructors and destructors (but they aren't implicitly or automatically invoked)
-
Function and method overloading
-
Templates without specialization. You should be able to access individual fields of the class or struct.
Unsupported Features
When bindgen
finds a type that is too difficult or impossible to translate
into Rust, it will automatically treat it as an opaque blob of bytes. The
philosophy is that
-
we should always get layout, size, and alignment correct, and
-
just because one type uses specialization, that shouldn't cause
bindgen
to give up on everything else.
Without further ado, here are C++ features that bindgen
does not support or
cannot translate into Rust:
-
Inline functions and methods: see "Why isn't
bindgen
generating bindings to inline functions?" -
Template functions, methods of template classes and structs. We don't know which monomorphizations exist, and can't create new ones because we aren't a C++ compiler.
-
Anything related to template specialization:
- Partial template specialization
- Traits templates
- Substitution Failure Is Not An Error (SFINAE)
-
Cross language inheritance, for example inheriting from a Rust struct in C++.
-
Automatically calling copy and/or move constructors or destructors. Supporting this isn't possible with Rust's move semantics.
-
Exceptions: if a function called through a
bindgen
-generated interface raises an exception that is not caught by the function itself, this will generate undefined behaviour. See the tracking issue for exceptions for more details. -
Many C++ specific aspects of calling conventions. For example in the Itanium abi types that are "non trivial for the purposes of calls" should be passed by pointer, even if they are otherwise eligible to be passed in a register. Similarly in both the Itanium and MSVC ABIs such types are returned by "hidden parameter", much like large structs in C that would not fit into a register. This also applies to types with any base classes in the MSVC ABI (see x64 calling convention). Because bindgen does not know about these rules generated interfaces using such types are currently invalid.
Constructor semantics
bindgen
will generate a wrapper for any class constructor declared in the
input headers. For example, this headers file
class MyClass {
public:
MyClass();
void method();
};
Will produce the following code:
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct MyClass {
pub _address: u8,
}
extern "C" {
#[link_name = "\u{1}_ZN7MyClass6methodEv"]
pub fn MyClass_method(this: *mut MyClass);
}
extern "C" {
#[link_name = "\u{1}_ZN7MyClassC1Ev"]
pub fn MyClass_MyClass(this: *mut MyClass);
}
impl MyClass {
#[inline]
pub unsafe fn method(&mut self) {
MyClass_method(self)
}
#[inline]
pub unsafe fn new() -> Self {
let mut __bindgen_tmp = ::std::mem::MaybeUninit::uninit();
MyClass_MyClass(__bindgen_tmp.as_mut_ptr());
__bindgen_tmp.assume_init()
}
}
This MyClass::new
Rust method can be used as a substitute for the MyClass
C++ constructor. However, the address of the value from inside the method will
be different than from the outside. This is because the __bindgen_tmp
value
is moved when the MyClass::new
method returns.
In contrast, the C++ constructor will not move the value, meaning that the
address of the value will be the same inside and outside the constructor.
If the original C++ relies on this semantic difference somehow, you should use the
MyClass_MyClass
binding directly instead of the MyClass::new
method.
In other words, the Rust equivalent for the following C++ code
MyClass instance = MyClass();
instance.method();
is not this
let instance = MyClass::new();
instance.method();
but this
let instance = std::mem::MaybeUninit::<MyClass>::uninit();
MyClass_MyClass(instance.as_mut_ptr());
instance.assume_init_mut().method();
You can easily verify this fact if you provide a implementation for MyClass
and method
that prints the this
pointer address. However, you can
ignore this fact if you know that the original C++ code does not rely on the
instance address in its internal logic.