GitHub crates.io docs.rs

autocxx — automatic safe interop between Rust and C++

Welcome to autocxx and thank you for reading!

Use autocxx if you have a large existing C++ codebase and you want to use its types and functions from Rust with maximal safety and minimal fuss.

autocxx is like bindgen, in that it enables you to use C++ functions and types from within Rust. But it automates a lot of the fiddly things you need to do with bindgen bindings: calling destructors, converting strings, unsafely handling raw pointers. C++ functions and types within autocxx bindings should behave naturally and ergonomically, almost as if they were safe Rust functions and types themselves. These ergonomics and safety improvements come from the cxx project - hence the name of this tool, autocxx.

autocxx combines the safety and ergonomics of cxx with the automatic bindings generation of bindgen. It stands on the shoulders of those giants!

When is autocxx the right tool?

Not always:

  • If you are making bindings to C code, as opposed to C++, use bindgen instead.
  • If you can make unrestricted changes to the C++ code, use cxx instead.
  • If your C++ to Rust interface is just a few functions or types, use cxx instead.

But sometimes:

  • If you need to call arbitrary functions and use arbitrary types within an existing C++ codebase, use autocxx. You're in the right place!
  • Like cxx, but unlike bindgen, autocxx helps with calls from C++ to Rust, too.

Examples to give you a feel for autocxx

Here's a code example:

C++ header:

#include <stdint.h>
inline uint32_t do_math(uint32_t a, uint32_t b) { return a+b; }

Rust:


// Use all the autocxx types which might be handy.
use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("do_math") // allowlist a function
}

fn main() {
    assert_eq!(ffi::do_math(12, 13), 25);
}

A more complex example:

C++ header:

#include <cstdint>
#include <string>

class Goat {
public:
    Goat();
    ~Goat();
    void add_a_horn();
    std::string describe() const;
private:
    uint32_t horns;
};

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("Goat") // allowlist a type and all its methods
}

fn main() {
    let mut goat = ffi::Goat::new().within_unique_ptr(); // returns a cxx::UniquePtr, i.e. a std::unique_ptr
    goat.pin_mut().add_a_horn();
    goat.pin_mut().add_a_horn();
    assert_eq!(goat.describe().as_ref().unwrap().to_string_lossy(), "This goat has 2 horns.");
}

This is typical autocxx code: the C++ objects behave much like Rust objects, but sometimes extra steps are required to handle cases like null pointers or converting strings that may not be UTF-8.

Still, fundamentally, you can interact with C++ objects without using unsafe in the majority of cases. cxx takes care of the fundamentals of lifetimes and destructors.

How to read this book

We'd recommend starting with tutorial and then workflow.

Tutorial

If you're here, you want to call some C++ from Rust, right?

Let's assume you're calling into some existing C++ code.

You will need:

  • Some C++ header files (.h files)
  • The C++ "include path". That is, the set of directories containing those headers. (That's not necessarily the directory in which each header file lives; C++ might contain #include "foo/bar.h" and so your include path would need to include the directory containing the foo directory).
  • A list of the APIs (types and functions) from those header files which you wish to make available in Rust.
  • To know how to link the C++ libraries into your Cargo project. This is beyond the scope of what autocxx helps with, but one solution is to emit a print from your build script.
  • LLVM to be installed.
  • Some patience. This is not a magic solution. C++/Rust interop is hard. Avoid it if you can!

The rest of this 'getting started' section assumes Cargo - if you're using something else, see the building section.

First, add autocxx and cxx to your dependencies and autocxx-build to your build-dependencies in your Cargo.toml. You must specify both.

[dependencies]
autocxx = "0.26.0"
cxx = "1.0"

[build-dependencies]
autocxx-build = "0.26.0"
miette = { version = "5", features = ["fancy"] } # optional but gives nicer error messages!

Now, add a build.rs next to your Cargo.toml (this is a standard cargo build script). This is where you need your include path:

fn main() -> miette::Result<()> {
    let path = std::path::PathBuf::from("src"); // include path
    let mut b = autocxx_build::Builder::new("src/main.rs", &[&path]).build()?;
        // This assumes all your C++ bindings are in main.rs
    b.flag_if_supported("-std=c++14")
     .compile("autocxx-demo"); // arbitrary library name, pick anything
    println!("cargo:rerun-if-changed=src/main.rs");
    // Add instructions to link to any C++ libraries you need.
    Ok(())
}

See the standard cargo build script output mechanisms for how you can direct Rust to link against pre-existing libraries, and see the building section of this book for more details about build options - for example, enabling C++17.

Finally, in your main.rs you can use the include_cpp macro which is the heart of autocxx:

use autocxx::prelude::*; // use all the main autocxx functions

include_cpp! {
    include "my_header.h" // your header file name
    safety!(unsafe) // see details of unsafety policies described in the 'safety' section of the book
    generate!("DeepThought") // add this line for each function or type you wish to generate
}

You should then find you can call the function by referring to an ffi namespace:

fn main() {
    println!("The answer to Life, The Universe and Everything is {}", ffi::DeepThought());
}

C++ types such as std::string and std::unique_ptr are represented using the types provided by the marvellous cxx library. This provides good ergonomics and safety norms, so unlike with normal bindgen bindings, you won't normally need to write unsafe code for every function call.

Next, read the section about workflows.

Workflow

C++ is complex, and autocxx can't ingest everything.

First tip - use an IDE. Type annotation and autocompletion is incredibly helpful in an autocxx context, where you may be dealing with UniquePtr<T> and Option<&T> and Pin<&mut T> very often. VSCode autocompletion of autocxx APIs

As you'll see, it's also essential when autocxx can't produce bindings for some reason.

What if autocxx can't generate bindings?

This bit is important.

When you use autocxx, you'll ask it to generate Rust bindings for C++ types or functions using generate! directives.

If you ask to generate bindings for a specific function, and it can't: the build will fail.

If you ask to generate bindings for an entire type, autocxx will generate bindings for as many methods as possible. For those methods where it can't generate bindings, it will instead generate some placeholder function or struct with documentation explaining what went wrong:

VSCode showing an error for an API where autocxx couldn't generate bindings

This is why it's crucial to use an IDE with autocxx.

How can I see what bindings autocxx has generated?

Options:

  • Use an IDE. (Did we mention, you should use an IDE?)
  • Run cargo doc --document-private-items.
  • Use cargo expand.

How to work around cases where autocxx can't generate bindings

Your options are:

  • Write extra C++ functions with simpler parameters or return types, and generate bindings to them, instead.
  • Write some manual #[cxx::bridge] bindings - see below.

Usually, you can solve problems by writing a bit of additional C++ code. For example, supposing autocxx can't understand your type Sandwich<Ham>. Instead it will give you a fairly useless opaque type such as Sandwich_Ham. You can write additional C++ functions to unpack the opaque type into something useful:

const Ham& get_filling(const Sandwich<Ham>& ham_sandwich);

Mixing manual and automated bindings

autocxx uses cxx underneath, and its build process will happily spot and process manually-crafted cxx::bridge mods which you include in your Rust source code. A common pattern could be to use autocxx to generate all the bindings possible, then hand-craft a cxx::bridge mod for the remainder where autocxx falls short.

To do this, you'll need to use the ability of one cxx::bridge mod to refer to types from another, for example:

autocxx::include_cpp! {
    include "foo.h"
    safety!(unsafe_ffi)
    generate!("take_A")
    generate!("A")
}
#[cxx::bridge]
mod ffi2 {
    unsafe extern "C++" {
        include!("foo.h");
        type A = crate::ffi::A;
        fn give_A() -> UniquePtr<A>; // in practice, autocxx could happily do this
    }
}
fn main() {
    let a = ffi2::give_A();
    assert_eq!(ffi::take_A(&a), autocxx::c_int(5));
}

In the example above, we're referring from manual bindings to automated bindings.

You can also do it the other way round using extern_cpp_opaque_type!:

autocxx::include_cpp! {
    hexathorpe include "input.h"
    safety!(unsafe_ffi)
    generate!("handle_a")
    generate!("create_a")
    extern_cpp_opaque_type!("A", ffi2::A)
}
#[cxx::bridge]
pub mod ffi2 {
    unsafe extern "C++" {
        include!("input.h");
        type A;
    }
    impl UniquePtr<A> {}
}
fn main() {
    let a = ffi::create_a();
    ffi::handle_a(&a);
}

My build entirely failed

autocxx should nearly always successfully parse the C++ codebase and generate some APIs. It's reliant on bindgen, but bindgen is excellent and rarely bails out entirely.

If it does, you may be able to use the block! macro.

We'd appreciate a minimized bug report of the troublesome code - see contributing.

Enabling autocompletion in a rust-analyzer IDE

You'll need to enable both:

  • Rust-analyzer: Proc Macro: Enable
  • Rust-analyzer: Experimental: Proc Attr Macros

Next steps

Now you've read what can go wrong with autocxx, and how to diagnose problems - the next step is to give it a try! Treat the rest of this manual as a reference.

The allowlist and include_cpp syntax

To include C++ in your Rust codebase using autocxx, you will need at least one include_cpp macro.

The simplest is:

use autocxx::prelude::*;

include_cpp! {
    include "my_header.h"
    generate!("MyAPIFunction")
}

You need to include generate! directives for every type or function you wish to access from Rust. You don't need to specify this for member functions of types that you've added - they'll be generated automatically. (If a particular member function can't be generated, some placeholder item with explanatory documentation will be generated instead).

Various other directives are possible inside this macro, most notably:

  • You can ask to generate all the items in a namespace using generate_ns!
  • You might sometimes want to ask that a type is generated as 'plain old data' using generate_pod! instead of generate! - see the chapter on C++ types.
  • You'll probaly want to specify a safety! policy

See the docs.rs documentation for the full list.

Building

Building if you're using cargo

The basics of building in a cargo environment are explained in the tutorial.

If your build depends on later editions of the C++ standard library, you will need to ensure that both libclang and the compiler are sent the appropriate flag, like this:

fn main() {
    let path = std::path::PathBuf::from("src"); // include path
    let mut b = autocxx_build::Builder::new("src/main.rs", &[&path])
        .extra_clang_args(&["-std=c++17"])
        .expect_build();
    b.flag_if_supported("-std=c++17") // use "-std:c++17" here if using msvc on windows
        .compile("autocxx-demo"); // arbitrary library name, pick anything
    println!("cargo:rerun-if-changed=src/main.rs");
    // Add instructions to link to any C++ libraries you need.
}

Building - if you're not using cargo

See the autocxx-gen crate. You'll need to:

  • Run the codegen phase. You'll need to use the autocxx-gen tool to process the .rs code into C++ header and implementation files. This will also generate .rs side bindings.
  • Educate the procedural macro about where to find the generated .rs bindings. Set the AUTOCXX_RS environment variable to a list of directories to search. If you use autocxx-build, this happens automatically. (You can alternatively specify AUTOCXX_RS_FILE to give a precise filename as opposed to a directory to search, though this isn't recommended unless your build system specifically requires it because it allows only a single include_cpp! block per .rs file.) See gen --help for details on the naming of the generated files.
flowchart TB
    s(Rust source with include_cpp!)
    c(Existing C++ headers)
    cg(autocxx-gen or autocxx-build)
    genrs(Generated .rs file)
    gencpp(Generated .cpp and .h files)
    rsb(Rust/Cargo build)
    cppb(C++ build)
    l(Linker)
    s --> cg
    c --> cg
    cg --> genrs
    cg --> gencpp
    m(autocxx-macro)
    s --> m
    genrs-. included .->m
    m --> rsb
    gencpp --> cppb
    cppb --> l
    rsb --> l

This interop inevitably involves lots of fiddly small functions. It's likely to perform far better if you can achieve cross-language link-time-optimization (LTO). This issue may give some useful hints - see also all the build-related help in the cxx manual which all applies here too.

C++ versions and other compiler command-line flags

The code generated by cxx and autocxx requires C++ 14, so it's not possible to use an earlier version of C++ than that.

To use a later version, you need to:

  • Build the generated code with a later C++ version, for example using the clang argument -std=c++17. If you're using autocxx's cargo support, then you would do this by calling methods on the returned cc::Build object, for instance flag_if_supported.
  • Also give similar directives to the C++ parsing which happens within autocxx (specifically, by autocxx's version of bindgen). To do that, use Builder::extra_clang_args.

The same applies with the command-line autocxx_gen support - you'll need to pass such extra compiler options to autocxx_gen and also use them when building the generated C++ code.

C++ structs, enums and classes

If you add a C++ struct, class or enum to the allowlist, Rust bindings will be generated to that type and to any methods it has. Even if you don't add it to the allowlist, the type may be generated if it's required by some other function - but in this case all its methods won't be generated.

Rust and C++ differ in an important way:

  • In Rust, the compiler is free to pick up some data and move it to somewhere else (in a memcpy sense). The object is none the wiser.
  • In C++, once created, an object stays where it is, until or unless it has its "move constructor" invoked.

This makes a big difference: C++ objects can have self-referential pointers, and any such pointer would be invalidated by Rust doing a memcpy. Such self-referential pointers are common - even some implementations of std::string do it.

POD and non-POD

When asking autocxx to generate bindings for a type, then, you have to make a choice.

  • This C++ type is trivial. It has no destructor or move constructor (or they're trivial), and thus Rust is free to move it around the stack as it wishes. autocxx calls these types POD ("plain old data"). Alternatively,
  • This C++ type has a non-trivial destructor or move constructor, so we can't allow Rust to move this around. autocxx calls these types non-POD.

POD types are nicer:

  • You can just use them as regular Rust types.
  • You get direct field access.
  • No funny business.

Non-POD types are awkward:

  • You can't just have one as a Rust variable. Normally you hold them in a cxx::UniquePtr, though there are other options.
  • There is no access to fields (yet).
  • You can't even have a &mut reference to one, because then you might be able to use std::mem::swap or similar. You can have a Pin<&mut> reference, which is more fiddly.

By default, autocxx generates non-POD types. You can request a POD type using generate_pod!. Don't worry: you can't mess this up. If the C++ type doesn't in fact comply with the requirements for a POD type, your build will fail thanks to some static assertions generated in the C++. (If you're really sure your type is freely relocatable, because you implemented the move constructor and destructor and you promise they're trivial, you can override these assertions using the C++ trait IsRelocatable per the instructions in cxx.h).

See the chapter on storage for lots more detail on how you can hold onto non-POD types.

Construction

Constructing a POD object is simple: call its new associated function. Bob's your uncle!

Multiple constructors (aka constructor overloading) follows the same rules as other functions.

Constructing a non-POD object requires two steps.

  • Call the new associated function in the same way. This will give you something implementing moveit::New/
  • Use this to make the object on the heap or stack, in any of the following ways:
Where you want to create itHow to create itWhat you getExample
C++ heap (recommended for simplicity)Within.within_unique_ptr() or UniquePtr::emplacecxx::UniquePtr<T>let mut obj = ffi::Goldfish::new().within_unique_ptr() or let mut obj = UniquePtr::emplace(ffi::Goldfish::new())
Rust heapWithin.within_box() or Box::emplacePin<Box<T>>let mut obj = ffi::Goldfish::new().within_box() or let mut obj = Box::emplace(ffi::Goldfish::new())
Rust stackmoveit macro&mut T (more or less)moveit! { let mut obj = ffi::Goldfish::new() }

For heap construction, the prefix (emplace) and postfix (.within_...) forms are exactly identical. Choose whichever suits your needs best.

Should you construct on the Rust heap or the C++ heap?

Use .within_unique_ptr() to create objects on the C++ heap. This gives you a cxx::UniquePtr<T> which works well with other autocxx and cxx APIs.

There is a small disadvantage - cxx::UniquePtr<T> is able to store NULL values. Therefore, each time you use the resulting object, there is an unwrap() (explicit or implicit). If this bothers you, use the Box option instead which can never be NULL.

Construction sounds complicated. Do you have a code example?

C++ header:

#include <stdint.h>
#include <string>
struct A {
    A() {}
    void set(uint32_t val);
    uint32_t get() const;
    uint32_t a;
};

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("A")
}

fn main() {
    moveit! {
        let mut stack_obj = ffi::A::new();
    }
    stack_obj.as_mut().set(42);
    assert_eq!(stack_obj.get(), 42);

    let mut heap_obj = ffi::A::new().within_unique_ptr();
    heap_obj.pin_mut().set(42);
    assert_eq!(heap_obj.get(), 42);

    let mut another_heap_obj = ffi::A::new().within_box();
    another_heap_obj.as_mut().set(42);
    assert_eq!(another_heap_obj.get(), 42);
}

Forward declarations

A type which is incomplete in the C++ headers (i.e. represented only by a forward declaration) can't be held in a UniquePtr within Rust (because Rust can't know if it has a destructor that will need to be called if the object is dropped.) Naturally, such an object can't be passed by value either; it can still be referenced in Rust references.

Generic (templated) types

If you're using one of the generic types which is supported natively by cxx, e.g. std::unique_ptr, it should work as you expect. For other generic types, we synthesize a concrete Rust type, corresponding to a C++ typedef, for each concrete instantiation of the type. Such generated types are always opaque, and never have methods attached. That's therefore enough to pass them between return types and parameters of other functions within cxx::UniquePtrs but not really enough to do anything else with these types yet1.

1

Future improvements tracked here

To make them more useful, you might have to add extra C++ functions to extract data or otherwise deal with them.

Usually, such concrete types are synthesized automatically because they're parameters or return values from functions. Very rarely, you may want to synthesize them yourself - you can do this using the concrete! directive. As noted, though, these types are currently opaque and fairly useless without passing them back and forth to C++, so this is not a commonly used facility. It does, however, allow you to give a more descriptive name to the type in Rust:

C++ header:

#include <string>
struct Tapioca {
  std::string yuck;
};
template<typename Floaters>
struct Tea {
  Tea() : floaters(nullptr) {}
  Floaters* floaters;
};
inline Tea<Tapioca> prepare() {
  Tea<Tapioca> mixture;
  // prepare...
  return mixture;
}
inline void drink(const Tea<Tapioca>&) {}

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("prepare")
    generate!("drink")
    concrete!("Tea<Tapioca>", Boba)
}

fn main() {
    let nicer_than_it_sounds: cxx::UniquePtr<ffi::Boba> = ffi::prepare();
    ffi::drink(&nicer_than_it_sounds);
}

Implicit member functions

Most of the API of a C++ type is contained within the type, so autocxx can understand what is available for Rust to call when that type is analyzed. However, there is an important exception for the so-called special member functions, which will be implicitly generated by the C++ compiler for some types. autocxx makes use of these types of special members:

  • Default constructor
  • Destructor
  • Copy constructor
  • Move constructor

Explicitly declared versions of these special members are easy: autocxx knows they exist and uses them.

autocxx currently uses its own analysis to determine when implicit versions of these exist. This analysis tries to be conservative (avoid generating wrappers that require the existence of C++ functions that don't exist), but sometimes this goes wrong and understanding the details is necessary to get the correct Rust wrappers generated.

In particular, determing whether an implicit version of any of these exists requires analyzing the types of all bases and members. autocxx only analyzes types when requested, because some may be un-analyzable. If the types of any bases or members are not analyzed, autocxx will assume a public destructor exists (in the absence of any other destructors), and avoid using any other implicit special member functions. Notably this includes the default constructor, so types with un-analyzed bases or members and no explicit constructors will not get a make_unique or new generated. If autocxx isn't generating a make_unique or CopyNew or MoveNew for a type which permits the corresponding operations in C++, make sure the types of all bases and members are analyzed or implement it explicitly.

autocxx currently does not take member initializers (const int x = 5) into account when determining whether a default constructor exists2. Explicitly declared default destructors still work though.

Currently, autocxx assumes that an explicitly defaulted (= default) member function exists, although it is valid C++ for that to be deleted3. Clang's -Wdefaulted-function-deleted flag (enabled by default) will warn about types like this.

A C++ type which can be instantiated but has an inaccessible constructor will be leaked by Rust4. The object's memory itself will be freed without calling any C++ destructor, which will leak any resources tracked by the C++ implementation.

Many of the special members may be overloaded in C++. This generally means adding const or volatile qualifiers or extra arguments with defaults. autocxx avoids using any overloaded special members because choosing which one to call from Rust gets tricky.

2

Handling of member initializers is tracked here.

3

Fix for explicitly defaulted special member functions that are deleted is tracked here.

4

Discussion around what to do about inaccessible or deleted destructors here.

Abstract types

autocxx does not allow instantiation of abstract types5 (aka types with pure virtual methods).

5

autocxx's determination of abstract types is a bit approximate and could be improved.

Pointers, references, values

autocxx knows how to deal with C++ APIs which take C++ types:

  • By value
  • By reference (const or not)
  • By raw pointer
  • By std::unique_ptr
  • By std::shared_ptr
  • By std::weak_ptr
  • By rvalue reference (that is, as a move parameter)

(all of this is because the underlying cxx crate has such versatility). Some of these have some quirks in the way they're exposed in Rust, described below.

Passing between C++ and Rust by value

See the section on C++ types for the distinction between POD and non-POD types. POD types can be passed around however you like. Non-POD types can be passed into functions in various ways - see calling C++ functions for more details.

References and pointers

We follow cxx norms here. Specifically:

  • A C++ reference becomes a Rust reference
  • A C++ pointer becomes a Rust pointer.
  • If a reference is returned with an ambiguous lifetime, we don't generate code for the function
  • Pointers require use of unsafe, references don't necessarily.

That last point is key. If your C++ API takes pointers, you're going to have to use unsafe. Similarly, if your C++ API returns a pointer, you'll have to use unsafe to do anything useful with the pointer in Rust. This is intentional: a pointer from C++ might be subject to concurrent mutation, or it might have a lifetime that could disappear at any moment. As a human, you must promise that you understand the constraints around use of that pointer and that's what the unsafe keyword is for.

Exactly the same issues apply to C++ references in theory, but in practice, they usually don't. Therefore cxx has taken the view that we can "trust" a C++ reference to a higher degree than a pointer, and autocxx follows that lead (in fact we 'trust' references even slightly more than cxx). In practice, of course, references are rarely return values from C++ APIs so we rarely have to navel-gaze about the trustworthiness of a reference.

(See also the discussion of safety - if you haven't specified an unsafety policy, all C++ APIs require unsafe so the discussion is moot.

If you're given a C++ object by pointer, and you want to interact with it, you'll need to figure out the guarantees attached to the C++ object - most notably its lifetime. To see some of the decision making process involved see the Steam example.

cxx::UniquePtrs tips

We use cxx::UniquePtr in completely the normal way, but there are a few quirks which you're more likely to run into with autocxx.

  • You'll need to use .pin_mut() a lot - see the example at the bottom of C++ functions.
  • If you need to pass a raw pointer to a function, lots of unsafety is required - something like this:
       let mut a = ffi::A::make_unique();
       unsafe { ffi::TakePointerToA(std::pin::Pin::<&mut ffi::A>::into_inner_unchecked(a.pin_mut())) };
    This may be simplified in future.

Storage - stack and heaps

Ensure you understand the distinction between POD and non-POD types described in the C++ types section before proceeding.

POD types

POD types are just regular Rust types! Store them on the stack, heap, in a Vec, a HashMap, whatever you want.

Non-POD types

Non-POD types can be stored:

  • In a cxx::UniquePtr. This is cxx's Rust wrapper for std::unique_ptr - so the object is stored in the C++ heap. Most of the time you handle a C++ object from autocxx, it will be stored in one of these.
  • In a Box - so the object is stored on the Rust heap. This has the advantage that there's no possibility that the object can be NULL.
  • On the Rust stack, using the autocxx::moveit macro.

If in doubt, use cxx::UniquePtr. It's simple and ergonomic.

See C++ types for a code example showing a type existing on both the stack and the heap.

Whose heap is it anyway?

Specifically cxx::UniquePtr is a binding to std::unique_ptr<T,std::default_delete<T>> which means the object will be deleted using the C++ delete operator. This will respect any overridden operator delete on the type, and similarly, the functions which autocxx provides to construct types should respect overridden operator new. This means: if your C++ type has code to create itself in some special or unusual heap partition, that should work fine.

Built-in types

autocxx relies primarily on the standard cxx types. In particular you should become familiar with cxx::UniquePtr and cxx::CxxString.

There are a few additional integer types, such as c_int, which are not yet upstreamed to cxx. These are to support those pesky C/C++ integer types which do not have a predictable number of bits on different machines.

C++ header:

inline int do_math(int a, int b) { return a+b; }

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("do_math")
}

fn main() {
    assert_eq!(ffi::do_math(c_int(12), c_int(13)), c_int(25));
}

Strings

autocxx uses cxx::CxxString. However, as noted above, we can't just pass a C++ string by value, so we'll box and unbox it automatically such that you're really dealing with UniquePtr<CxxString> on the Rust side, even if the API just took or returned a plain old std::string.

However, to ease ergonomics, functions that accept a std::string will actually accept anything that implements a trait called ffi::ToCppString. That may either be a UniquePtr<CxxString> or just a plain old Rust string - which will be converted transparently to a C++ string.

This trait, and its implementations, are not present in the autocxx documentation because they're dynamically generated in your code so that they can call through to a make_string implementation in the C++ that we're injecting into your C++ build system.

(None of that happens if you use exclude_utilities, so don't do that.)

C++ header:

#include <string>
#include <cstdint>
inline uint32_t take_string(std::string a) { return a.size(); }

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("take_string")
}

fn main() {
    assert_eq!(ffi::take_string("hello"), 5)
}

If you need to create a blank UniquePtr<CxxString> in Rust, such that (for example) you can pass its mutable reference or pointer into some pre-existing C++ API, call ffi::make_string("") which will return a blank UniquePtr<CxxString>.

If all you need is a reference to a CxxString, you can alternatively use cxx::let_cxx_string.

Naming

Namespaces

The C++ namespace structure is reflected in mods within the generated ffi mod. However, at present there is an internal limitation that autocxx can't handle multiple types with the same identifier, even if they're in different namespaces. This will be fixed in future.

C++ header:


namespace generations {
  void hey_boomer();
}
namespace submarines {
  void hey_boomer();
}

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("submarines::hey_boomer")
    generate!("generations::hey_boomer")
}

fn main() {
    ffi::generations::hey_boomer(); // insults your elders and betters
    ffi::submarines::hey_boomer(); // launches missiles
}

Nested types

There is support for generating bindings of nested types, with some restrictions. Currently the C++ type A::B will be given the Rust name A_B in the same module as its enclosing namespace.

C++ header:


struct Turkey {
    struct Duck {
        struct Hen {
            int wings;
        };
    };
};

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate_pod!("Turkey_Duck_Hen")
}

fn main() {
    let _turducken = ffi::Turkey_Duck_Hen::new().within_box();
}

Overloads

See the chapter on C++ functions.

C++ functions

Calling C++ functions is largly as you might expect.

Value and rvalue parameters

Functions taking non-POD value parameters can take a cxx::UniquePtr<T> or a &T. This gives you the choice of Rust semantics - where a parameter is absorbed and destroyed - or C++ semantics where the parameter is copied.

C++ header:

#include <cstdint>

struct Goat {
    Goat();
    uint32_t horn_count;
};

void feed_goat(Goat g); // takes goat by value

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("Goat")
    generate!("feed_goat")
}

fn main() {
    let goat = ffi::Goat::new().within_unique_ptr(); // returns a cxx::UniquePtr, i.e. a std::unique_ptr
    // C++-like semantics...
    ffi::feed_goat(&goat);
    // ... you've still got the goat!
    ffi::feed_goat(&goat);
    // Or, Rust-like semantics, where the goat is consumed.
    ffi::feed_goat(goat);
    // No goat any more...
    // ffi::feed_goat(&goat); // doesn't compile
}

Specifically, you can pass anything which implements ValueParam<T>.

If you're keeping non-POD values on the Rust stack, you need to explicitly use as_mov to indicate that you want to consume the object using move semantics:

C++ header:

#include <cstdint>

struct Blimp {
    Blimp();
    uint32_t tons_of_helium;
};

void burst(Blimp b); // consumes blimp,
    // but because C++ is amazing, may copy the blimp first.

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("Blimp")
    generate!("burst")
}

fn main() {
    moveit! {
        let mut blimp = ffi::Blimp::new();
    }
    ffi::burst(&*blimp); // pass by copy
    ffi::burst(as_copy(blimp.as_ref())); // explicitly say you want to pass by copy
    ffi::burst(as_mov(blimp)); // consume, using move constructor
}

RValue parameters are a little simpler, because (as you'd hope) they consume the object you're passing in.

C++ header:

#include <cstdint>

struct Cake {
    Cake();
    uint32_t tons_of_sugar;
};

void eat(Cake&& c); // consumes the cake. You can't have your cake and eat it.

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("Cake")
    generate!("eat")
}

fn main() {
    moveit! {
        let mut stack_cake = ffi::Cake::new();
    }
    ffi::eat(stack_cake);
    // No more cake.

    // Still peckish.
    let heap_cake = ffi::Cake::new().within_unique_ptr();
    ffi::eat(heap_cake);
    // Really no more cake now.
}

Default parameters

Are not yet supported1.

1

the work is planned here.

Return values

Any C++ function which returns a non-POD type to Rust in fact gives you an opaque object implementing moveit::New. This enables you to "emplace" the resulting object either on the stack or heap, in exactly the same way as if you're constructying an object. See the section on construction for how to turn this opaque object into something useful (spoiler: just append .within_unique_ptr()).

Overloads - and identifiers ending in digits

C++ allows function overloads; Rust doesn't. autocxx follows the lead of bindgen here and generating overloads as func, func1, func2 etc. This is essentially awful without rust-analyzer IDE support - see the workflows chapter for why you should be using an IDE.

C++ header:


#include <string>
struct View {
    std::string of_what; // go and watch In Bruges, it's great
};

struct Tree {
    int dendrochronologically_determined_age;
};

void saw(const View&);
void saw(const Tree&);

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("Tree")
    generate!("View")
    generate!("saw")
    generate!("saw1")
}

fn main() {
    let view = ffi::View::new().within_unique_ptr();
    ffi::saw(&view);
    let tree = ffi::Tree::new().within_unique_ptr();
    ffi::saw1(&tree); // yuck, overload
}

autocxx doesn't yet support default parameters.

It's fairly likely we'll change the model here in the future, such that we can pass tuples of different parameter types into a single function implementation.

Methods

Calling a const method is simple:

C++ header:


class Sloth {
public:
    void sleep() const {} // sloths unchanged by sleep
};

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("Sloth")
}

fn main() {
    let sloth = ffi::Sloth::new().within_unique_ptr();
    sloth.sleep();
    sloth.sleep();
}

Calling a non-const method is a bit more of a pain. Per cxx norms, all mutable references to C++ objects must be pinned. In practice, this means you must call .pin_mut() every time you call a method:

C++ header:


class Sloth {
public:
    void unpeel_from_tree() {} // sloths get agitated when removed from
        // trees, probably shouldn't be const
};

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("Sloth")
}

fn main() {
    let mut sloth = ffi::Sloth::new().within_unique_ptr();
    sloth.pin_mut().unpeel_from_tree();
    sloth.pin_mut().unpeel_from_tree();
}

Callbacks into Rust

autocxx is primarily to allow calls from Rust to C++, but like cxx it also allows you to expose Rust APIs to C++.

You can:

This latter option is most commonly used for implementing "listeners" or "observers", so is often in practice how C++ will call into Rust. More details below.

Subclasses

There is limited and experimental support for creating Rust subclasses of C++ classes. (Yes, even more experimental than all the rest of this!) See subclass::CppSubclass for information about how you do this. This is useful primarily if you want to listen out for messages broadcast using the C++ observer/listener pattern.

C++ header:

class GoatObserver {
public:
    virtual void goat_full() const = 0;
    virtual ~GoatObserver() {}
};

void register_observer(const GoatObserver& observer);
void deregister_observer();
void feed_goat();

Rust:


use autocxx::prelude::*;
use autocxx::subclass::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    generate!("register_observer")
    generate!("deregister_observer")
    generate!("feed_goat")
    subclass!("GoatObserver", MyGoatObserver)
}

use ffi::*;

#[subclass]
#[derive(Default)]
pub struct MyGoatObserver;

impl GoatObserver_methods for MyGoatObserver {
    fn goat_full(&self) {
        println!("BURP!");
    }
}

impl Drop for MyGoatObserver {
    fn drop(&mut self) {
        deregister_observer();
    }
}

fn main() {
    let goat_obs = MyGoatObserver::default_rust_owned();
    // Register a reference to the superclass &ffi::GoatObserver
    register_observer(goat_obs.as_ref().borrow().as_ref());
    feed_goat();
    feed_goat();
    feed_goat(); // prints BURP!
}

Subclass ownership

See subclass::CppSubclass for full details, but you must decide who owns your subclass:

  • C++ owns it
  • Rust owns it
  • It's self-owned, and only ever frees itself (using delete_self).

Please be careful: the observer pattern is a minefield for use-after-free bugs. It's recommended that you wrap any such subclass in some sort of Rust newtype wrapper which enforces any ownership invariants so that users of your types literally can't make any mistakes.

Calling superclass methods

Each subclass also implements a trait called <superclass name>_supers which includes all superclass methods. You can call methods on that, and if you don't implement a particular method, that will be used as the default.

C++ header:


#include <iostream>
class Dinosaur {
public:
    Dinosaur() {}
    virtual void eat() const {
        std::cout << "Roarrr!! I ate you!\n";
    }
    virtual ~Dinosaur() {}
};


Rust:


use autocxx::prelude::*;
use autocxx::subclass::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe_ffi)
    subclass!("Dinosaur", TRex)
    subclass!("Dinosaur", Diplodocus)
}

use ffi::*;

#[subclass]
#[derive(Default)]
pub struct TRex;

#[subclass]
#[derive(Default)]
pub struct Diplodocus;

impl Dinosaur_methods for TRex {
    // TRex does NOT implement the 'eat' method
    // so C++ behavior will be used
}

impl Dinosaur_methods for Diplodocus {
    fn eat(&self) {
        println!("Ahh, some nice juicy leaves.");
        // Could call self.eat_super() if we
        // developed unexpected carnivorous cravings.
    }
}

fn main() {
    let trex = TRex::default_rust_owned();
    trex.borrow().as_ref().eat(); // eats human
    let diplo = Diplodocus::default_rust_owned();
    diplo.borrow().as_ref().eat(); // eats shoots and leaves
}

Subclass casting

Subclasses implement AsRef to enable casting to superclasses.

Other C++ features

You can make Rust subclasses of C++ classes - as these are mostly used to implement the Observer pattern, they're documented under calls from C++ to Rust.

Exceptions

Exceptions are not supported. If your C++ code is compiled with exceptions, you can expect serious runtime explosions. The underlying cxx crate has exception support, so it would be possible to add them.

Preprocessor symbols

#define and other preprocessor symbols will appear as constants. At present there is no way to do compile-time disablement of code (equivalent of #ifdef)1.

1

This feature should add ifdef support.

String constants

Whether from a preprocessor symbol or from a C++ char* constant, strings appear as [u8] with a null terminator. To get a Rust string, do this:

#define BOB "Hello"
# mod ffi { pub static BOB: [u8; 6] = [72u8, 101u8, 108u8, 108u8, 111u8, 0u8]; }
assert_eq!(std::str::from_utf8(&ffi::BOB).unwrap().trim_end_matches(char::from(0)), "Hello");

Safety

Unsafety policies

By default, every autocxx function is unsafe. That means you can only call C++ functions from unsafe blocks, and it's up to you to be sure that the C++ code upholds the invariants the Rust compiler expects.

You can optionally specify:

safety!(unsafe)

within your include_cpp! macro invocation. If you do this, you are promising the Rust compiler that all your C++ function calls are upholding the invariants which rustc expects, and thus each individual function is no longer unsafe.

See safety! in the documentation for more details.

Examples with and without safety!(unsafe)

Without a safety! directive:

C++ header:

#include <cstdint>
inline uint32_t do_math(uint32_t a, uint32_t b) { return a+b; }

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    generate!("do_math")
}

fn main() {
    assert_eq!(unsafe { ffi::do_math(12, 13) }, 25);
}

With a safety! directive:

C++ header:

#include <cstdint>
inline uint32_t do_math(uint32_t a, uint32_t b) { return a+b; }

Rust:


use autocxx::prelude::*;

include_cpp! {
    #include "input.h"
    safety!(unsafe)
    generate!("do_math")
}

fn main() {
    assert_eq!(ffi::do_math(12, 13), 25);
}

Pragmatism in a complex C++ codebase

This crate mostly intends to follow the lead of the cxx crate in where and when unsafe is required. But, this crate is opinionated. It believes some unsafety requires more careful review than other bits, along the following spectrum:

  • Rust unsafe code (requires most review)
  • Rust code calling C++ with raw pointers
  • Rust code calling C++ with shared pointers, or anything else where there can be concurrent mutation
  • Rust code calling C++ with unique pointers, where the Rust single-owner model nearly always applies (but we can't prove that the C++ developer isn't doing something weird)
  • Rust safe code (requires least review)

If your project is 90% Rust code, with small bits of C++, don't use this crate. You need something where all C++ interaction is marked with big red "this is terrifying" flags. This crate is aimed at cases where there's 90% C++ and small bits of Rust, and so we want the Rust code to be pragmatically reviewable without the signal:noise ratio of unsafe in the Rust code becoming so bad that unsafe loses all value.

Worked example

Imagine you have this C++:

struct Thing;
void print_thing(const Thing& thing);

By using autocxx (or cxx), you're promising the Rust compiler that the print_thing function does sensible things with that reference:

  • It doesn't store a pointer to the thing anywhere and pass it back to Rust later.
  • It doesn't mutate it.
  • It doesn't delete it.
  • or any of the other things that you're not permitted to do in unsafe Rust.

Soundness

This crate shares the general approach to safety and soundness pioneered by cxx, but has two important differences:

  • cxx requires you to specify your interface in detail, and thus think through all aspects of the language boundary. autocxx doesn't, and may autogenerate footguns.
  • cxx may allow multiple conflicting Rust references to exist to 'trivial' data types ("plain old data" or POD in autocxx parlance), but they're rare. autocxx may allow conflicting Rust references to exist even to 'opaque' (non-POD) data, and they're more common. This difference exists because opaque data is zero-sized in cxx, and zero-sized references cannot conflict. (In autocxx, we tell Rust about the size in order that we can allocate such types on the stack.)

There are preliminary explorations to avoid this problem by using a C++ reference wrapper type. See examples/reference-wrappers.

Using the generated bindings

Congratulations, you've built some bindings using autocxx!

But are they Rustic? How can you ensure that users of the bindings get Rust-like safety?

The C++ API may have documented usage invariants. Your ideal is to encode as many as possible of those into compile-time checks in Rust.

Some options to consider:

  • Wrap the bindings in a newtype wrapper which enforces compile-time variants in its APIs; for example, taking a mutable reference to enforce exclusive access.
  • Add extra impl blocks to add methods with a more Rustic API.
  • Read the C++ to Rust design FAQ.

Structuring a large codebase

If you have multiple modules, it may be inconvenient to generate all bindings in one include_cpp! invocation. There is limited support to refer from one set of bindings to another, using the extern_cpp_type! directive.

C++ header:

#include <cstdint>
struct A {
    A() : a(0) {}
    int a;
};
enum B {
    VARIANT,
};
void handle_a(const A& a);
A create_a(B);

Rust:


use autocxx::prelude::*;

pub mod base {
    autocxx::include_cpp! {
        #include "input.h"
        name!(ffi2)
        safety!(unsafe_ffi)
        generate!("A")
        generate!("B")
    }
    pub use ffi2::*;
}
pub mod dependent {
    autocxx::include_cpp! {
        #include "input.h"
        safety!(unsafe_ffi)
        generate!("handle_a")
        generate!("create_a")
        extern_cpp_type!("A", crate::base::A)
        extern_cpp_type!("B", super::super::base::B)
        pod!("B")
    }
    pub use ffi::*;
}
fn main() {
    let a = dependent::create_a(base::B::VARIANT).within_unique_ptr();
    dependent::handle_a(&a);
}

Examples

Contributions of more examples to the examples directory are much appreciated!

Credits

David Tolnay did much of the hard work here, by inventing the underlying cxx crate, and in fact nearly all of the parsing infrastructure on which this crate depends. bindgen is also awesome. This crate stands on the shoulders of giants!

Miguel Young also did all the hard thinking about whether non-trivial C++ objects can safely exist on the Rust stack. They can! He also draws nifty cartoons.

Thanks to all the other contributors to cxx, bindgen and autocxx.

And thanks to all in the Chromium community for inspiring this tool.

How to Contribute

We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow.

Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project. Head over to https://cla.developers.google.com/ to see your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again.

Code reviews

All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult GitHub Help for more information on using pull requests.

Community Guidelines

This project follows Google's Open Source Community Guidelines.

Directory structure

  • book - you're reading it!
  • demo - a very simple demo example
  • examples - will gradually fill with more complex examples
  • parser - code which parses a single include_cpp! macro. Used by both the macro (which doesn't do much) and the code generator (which does much more, by means of engine below)
  • engine - all the core code for actual code generation.
  • macro - the procedural macro which expands the Rust code.
  • gen/build - a library to be used from build.rs scripts to generate .cc and .h files from an include_cxx section.
  • gen/cmd - a command-line tool which does the same.
  • src (outermost project) - a wrapper crate which imports the procedural macro and a few other things.

Where to start reading

The main algorithm is in engine/src/lib.rs, in the function generate(). This asks bindgen to generate a heap of Rust code and then passes it into engine/src/conversion to convert it to be a format suitable for input to cxx.

However, most of the actual code is in engine/src/conversion/mod.rs.

At the moment we're using a slightly branched version of bindgen called autocxx-bindgen. It's hoped this is temporary; some of our changes are sufficiently weird that it would be presumptious to try to get them accepted upstream until we're sure autocxx has roughly the right approach.

How to develop

If you're making a change, here's what you need to do to get useful diagnostics etc. First of all, cargo run in the demo directory. If it breaks, you don't get much in the way of useful diagnostics, because stdout is swallowed by cargo build scripts. So, practically speaking, you would almost always move onto running one of the tests in the test suite. With suitable options, you can get plenty of output. For instance:

RUST_BACKTRACE=1 RUST_LOG=autocxx_engine=info cargo test --all test_cycle_string_full_pipeline -- --nocapture

This is especially valuable to see the bindgen output Rust code, and then the converted Rust code which we pass into cxx. Usually, most problems are due to some mis-conversion somewhere in engine/src/conversion. See here for documentation and diagrams on how the engine works.

You may also wish to set AUTOCXX_ASAN=1 on Linux when running tests. To exercise all the code paths related to generating both C++ and Rust side shims, you can set AUTOCXX_FORCE_WRAPPER_GENERATION=1. The test suite doesn't do this by default because we also want to test the normal code paths. (In the future we might want to parameterize the test suite to do both.)

Reporting bugs

If you've found a problem, and you're reading this, thank you! Your diligence in reporting the bug is much appreciated and will make autocxx better. In order of preference here's how we would like to hear about your problem:

  • Raise a pull request adding a new failing integration test to integration_test.rs
  • Minimize the test using tools/reduce, something like this: target/debug/autocxx-reduce file -d "safety!(unsafe_ffi)" -d 'generate_pod!("A")' -I ~/my-include-dir -h my-header.h -p problem-error-message -- --remove-pass pass_line_markers This is a wrapper for the amazing creduce which will take thousands of lines of C++, preprocess it, and then identify the minimum required lines to reproduce the same problem.
  • Use the C++ preprocessor to give a single complete C++ file which demonstrates the problem, along with the include_cpp! directive you use. Alternatively, run your build using AUTOCXX_REPRO_CASE=repro.json which should put everything we need into output.h. If necessary, you can use the CLANG_PATH or CXX environment variables to specify the path to the Clang compiler to use.
  • Failing all else, build using cargo clean -p <your package name> && RUST_LOG=autocxx_engine=info cargo build -vvv and send the entire log to us. This will include two key bits of logging: the C++ bindings as distilled by bindgen, and then the version which we've converted and moulded to be suitable for use by cxx.

Unfortunately, linking C++ binaries is a complex area subject in itself, and we won't be able to debug your linking issues by means of an autocxx bug report. Assuming you're using autocxx's build.rs support, the actual C++ build and managed by the cc crate. You can find many of its options on its Build type. If you need to bring in an external library, you may also need to emit certain print statements from your build.rs to instruct cargo to link against that library.

How to contribute to this manual

More examples in this manual are very welcome!

Because autocxx examples require both Rust and C++ code to be linked together, a custom preprocessor is used for this manual. See one of the existing examples such as in index.md to see how to do this.

Google Open Source Community Guidelines

At Google, we recognize and celebrate the creativity and collaboration of open source contributors and the diversity of skills, experiences, cultures, and opinions they bring to the projects and communities they participate in.

Every one of Google's open source projects and communities are inclusive environments, based on treating all individuals respectfully, regardless of gender identity and expression, sexual orientation, disabilities, neurodiversity, physical appearance, body size, ethnicity, nationality, race, age, religion, or similar personal characteristic.

We value diverse opinions, but we value respectful behavior more.

Respectful behavior includes:

  • Being considerate, kind, constructive, and helpful.
  • Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or physically threatening behavior, speech, and imagery.
  • Not engaging in unwanted physical contact.

Some Google open source projects may adopt an explicit project code of conduct, which may have additional detailed expectations for participants. Most of those projects will use our modified Contributor Covenant.

Resolve peacefully

We do not believe that all conflict is necessarily bad; healthy debate and disagreement often yields positive results. However, it is never okay to be disrespectful.

If you see someone behaving disrespectfully, you are encouraged to address the behavior directly with those involved. Many issues can be resolved quickly and easily, and this gives people more control over the outcome of their dispute. If you are unable to resolve the matter for any reason, or if the behavior is threatening or harassing, report it. We are dedicated to providing an environment where participants feel welcome and safe.

Reporting problems

Some Google open source projects may adopt a project-specific code of conduct. In those cases, a Google employee will be identified as the Project Steward, who will receive and handle reports of code of conduct violations. In the event that a project hasn’t identified a Project Steward, you can report problems by emailing opensource@google.com.

We will investigate every complaint, but you may not receive a direct response. We will use our discretion in determining when and how to follow up on reported incidents, which may range from not taking action to permanent expulsion from the project and project-sponsored spaces. We will notify the accused of the report and provide them an opportunity to discuss it before any action is taken. The identity of the reporter will be omitted from the details of the report supplied to the accused. In potentially harmful situations, such as ongoing harassment or threats to anyone's safety, we may take action without notice.

This document was adapted from the IndieWeb Code of Conduct and can also be found at https://opensource.google/conduct/.