extern "C++"
The extern "C++"
section of a CXX bridge declares C++ types and signatures to
be made available to Rust, and gives the paths of the header(s) which contain
the corresponding C++ declarations.
A bridge module may contain zero or more extern "C++" blocks.
Opaque C++ types
Type defined in C++ that are made available to Rust, but only behind an indirection.
For example in the Tutorial we saw BlobstoreClient
implemented as an opaque C++ type. The blobstore client was created in C++ and
returned to Rust by way of a UniquePtr.
Mutability: Unlike extern Rust types and shared types, an extern C++ type is
not permitted to be passed by plain mutable reference &mut MyType
across the
FFI bridge. For mutation support, the bridge is required to use Pin<&mut MyType>
. This is to safeguard against things like mem::swap-ing the contents of
two mutable references, given that Rust doesn't have information about the size
of the underlying object and couldn't invoke an appropriate C++ move constructor
anyway.
Thread safety: Be aware that CXX does not assume anything about the thread
safety of your extern C++ types. In other words the MyType
etc bindings which
CXX produces for you in Rust do not come with Send
and Sync
impls. If you
are sure that your C++ type satisfies the requirements of Send
and/or Sync
and need to leverage that fact from Rust, you must provide your own unsafe
marker trait impls.
Take care in doing this because thread safety in C++ can be extremely tricky to
assess if you are coming from a Rust background. For example the
BlobstoreClient
type in the tutorial is not thread safe despite doing only
completely innocuous things in its implementation. Concurrent calls to the tag
member function trigger a data race on the blobs
map.
Functions and member functions
This largely follows the same principles as extern
"Rust" functions and methods. In particular, any signature
with a self
parameter is interpreted as a C++ non-static member function and
exposed to Rust as a method.
The programmer does not need to promise that the signatures they have typed in are accurate; that would be unreasonable. CXX performs static assertions that the signatures exactly correspond with what is declared in C++. Rather, the programmer is only on the hook for things that C++'s static information is not precise enough to capture, i.e. things that would only be represented at most by comments in the C++ code unintelligible to a static assertion: namely whether the C++ function is safe or unsafe to be called from Rust.
Safety: the extern "C++" block is responsible for deciding whether to expose
each signature inside as safe-to-call or unsafe-to-call. If an extern block
contains at least one safe-to-call signature, it must be written as an unsafe extern
block, which serves as an item level unsafe block to indicate that an
unchecked safety claim is being made about the contents of the block.
Lifetimes
C++ types holding borrowed data may be described naturally in Rust by an extern type with a generic lifetime parameter. For example in the case of the following pair of types:
we'd want to expose this to Rust as:
Reusing existing binding types
Extern C++ types support a syntax for declaring that a Rust binding of the correct C++ type already exists outside of the current bridge module. This avoids generating a fresh new binding which Rust's type system would consider non-interchangeable with the first.
In this case rather than producing a unique new Rust type ffi::MyType
for the
Rust binding of C++'s ::path::to::MyType
, CXX will reuse the already existing
binding at crate::existing::MyType
in expressing the signature of f
and any
other uses of MyType
within the bridge module.
CXX safely validates that crate::existing::MyType
is in fact a binding for the
right C++ type ::path::to::MyType
by generating a static assertion based on
crate::existing::MyType
's implementation of ExternType
, which is a trait
automatically implemented by CXX for bindings that it generates but can also be
manually implemented as described below.
ExternType
serves the following two related use cases.
Safely unifying occurrences of an extern type across bridges
In the following snippet, two #[cxx::bridge] invocations in different files
(possibly different crates) both contain function signatures involving the same
C++ type example::Demo
. If both were written just containing type Demo;
,
then both macro expansions would produce their own separate Rust type called
Demo
and thus the compiler wouldn't allow us to take the Demo
returned by
file1::ffi::create_demo
and pass it as the Demo
argument accepted by
file2::ffi::take_ref_demo
. Instead, one of the two Demo
s has been defined as
an extern type alias of the other, making them the same type in Rust.
Integrating with bindgen-generated or handwritten unsafe bindings
Handwritten ExternType
impls make it possible to plug in a data structure
emitted by bindgen as the definition of a C++ type emitted by CXX.
By writing the unsafe ExternType
impl, the programmer asserts that the C++
namespace and type name given in the type id refers to a C++ type that is
equivalent to Rust type that is the Self
type of the impl.
The ExternType::Id
associated type encodes a type-level representation of the
type's C++ namespace and type name. It will always be defined using the
type_id!
macro exposed in the cxx crate.
The ExternType::Kind
associated type will always be either
cxx::kind::Opaque
or cxx::kind::Trivial
identifying whether a C++ type
is soundly relocatable by Rust's move semantics. A C++ type is only okay to hold
and pass around by value in Rust if its move constructor is trivial and it has
no destructor. In CXX, these are called Trivial extern C++ types, while types
with nontrivial move behavior or a destructor must be considered Opaque and
handled by Rust only behind an indirection, such as a reference or UniquePtr.
If you believe your C++ type reflected by the ExternType impl is indeed fine to hold by value and move in Rust, you can specify:
which will enable you to pass it into C++ functions by value, return it by
value, and include it in struct
s that you have declared to cxx::bridge
. Your
claim about the triviality of the C++ type will be checked by a static_assert
in the generated C++ side of the binding.
Explicit shim trait impls
This is a somewhat niche feature, but important when you need it.
CXX's support for C++'s std::unique_ptr and std::vector is built on a set of internal trait impls connecting the Rust API of UniquePtr and CxxVector to underlying template instantiations performed by the C++ compiler.
When reusing a binding type across multiple bridge modules as described in the previous section, you may find that your code needs some trait impls which CXX hasn't decided to generate.
You can request a specific template instantiation at a particular location in
the Rust crate hierarchy by writing impl UniquePtr<A> {}
inside of the bridge
module which defines A
but does not otherwise contain any use of
UniquePtr<A>
.