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 unlikebindgen
,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 thefoo
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.27.0"
cxx = "1.0"
[build-dependencies]
autocxx-build = "0.27.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.
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:
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 ofgenerate!
- 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 theautocxx-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 theAUTOCXX_RS
environment variable to a list of directories to search. If you useautocxx-build
, this happens automatically. (You can alternatively specifyAUTOCXX_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 singleinclude_cpp!
block per.rs
file.) Seegen --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 returnedcc::Build
object, for instanceflag_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 usestd::mem::swap
or similar. You can have aPin<&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 implementingmoveit::New
/ - Use this to make the object on the heap or stack, in any of the following ways:
Where you want to create it | How to create it | What you get | Example |
---|---|---|---|
C++ heap (recommended for simplicity) | Within.within_unique_ptr() or UniquePtr::emplace | cxx::UniquePtr<T> | let mut obj = ffi::Goldfish::new().within_unique_ptr() or let mut obj = UniquePtr::emplace(ffi::Goldfish::new()) |
Rust heap | Within.within_box() or Box::emplace | Pin<Box<T>> | let mut obj = ffi::Goldfish::new().within_box() or let mut obj = Box::emplace(ffi::Goldfish::new()) |
Rust stack | moveit 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::UniquePtr
s
but not really enough to do anything else with these types yet1.
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.
Handling of member initializers is tracked here.
Fix for explicitly defaulted special member functions that are deleted is tracked here.
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).
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::UniquePtr
s 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:
This may be simplified in future.let mut a = ffi::A::make_unique(); unsafe { ffi::TakePointerToA(std::pin::Pin::<&mut ffi::A>::into_inner_unchecked(a.pin_mut())) };
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 forstd::unique_ptr
- so the object is stored in the C++ heap. Most of the time you handle a C++ object fromautocxx
, 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.
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:
- Declare that Rust types should be available to C++ using
extern_rust_type
- Make Rust functions available to C++ using
extern_rust_function
. - Allow Rust subclasses of C++ classes.
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.
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
- Demo - simplest possible demo
- S2 example - example using S2 geometry library
- Steam example - example using (something like) the Steam client library
- Subclass example - example using subclasses
- Integration tests
- hundreds of small snippets
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 exampleexamples
- will gradually fill with more complex examplesparser
- code which parses a singleinclude_cpp!
macro. Used by both the macro (which doesn't do much) and the code generator (which does much more, by means ofengine
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 frombuild.rs
scripts to generate .cc and .h files from aninclude_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 amazingcreduce
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 usingAUTOCXX_REPRO_CASE=repro.json
which should put everything we need intooutput.h
. If necessary, you can use theCLANG_PATH
orCXX
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 bybindgen
, and then the version which we've converted and moulded to be suitable for use bycxx
.
Bugs related to linking problems
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/.