autocxx

repository·main·Indexed 25 days ago

https://github.com/google/autocxx

A tool for calling C++ from Rust that combines the automation of bindgen with the safety model of cxx. It provides the `include_cpp!` macro for generating bindings from C++ headers and supports both POD and non-POD C++ types. The library includes tools like `autocxx-reduce` for minimizing reproduction cases and `autocxx-gen` for manual code generation outside of Cargo.

Tokens
15.9K
Snippets
23
Records
88
Agent score
80%

What's inside autocxx

  1. Overview of autocxx

    main

    autocxx is a tool for automatic, safe interop between Rust and C++. It combines the automatic bindings generation capabilities of bindgen with the safety and ergonomics of cxx.

    Key features include:

    • Automating fiddly tasks like calling destructors, converting strings, and handling raw pointers.
    • Providing ergonomic C++ types and functions that behave similarly to safe Rust types.
    • Supporting calls from both Rust to C++ and C++ to Rust.

    Use autocxx when you have a large existing C++ codebase and want to use its types and functions from Rust with minimal manual binding effort.

  2. What is Autocxx?

    main
    Autocxx is a tool designed to allow calling C++ from Rust in a heavily automated and safe manner. It aims to provide the fluent safety guarantees found in cxx while automating the generation of interfaces from existing C++ headers using a variant of bindgen. Conceptually, autocxx acts as glue that integrates the automation of bindgen with the safety model of cxx.
  3. Handle C++ strings with `ToCppString` ergonomics

    main

    When a C++ function accepts a std::string, autocxx provides ergonomic support via the ffi::ToCppString trait. This allows you to pass plain Rust strings directly to the generated FFI functions. Under the hood, autocxx transparently converts the Rust string into a UniquePtr<CxxString> using injected C++ utilities.

    Note: This functionality requires the default utility injection. If you use exclude_utilities, these traits will not be available.

  4. Handle generic (templated) C++ types in Rust

    main

    For generic types not natively supported by cxx (like std::unique_ptr), autocxx synthesizes a concrete, opaque Rust type for every unique C++ instantiation.

    • Limitations: These synthesized types are opaque and do not have methods attached. They are primarily useful for passing them between functions as parameters or return values within cxx::UniquePtrs.
    • Custom Naming: You can use the concrete! directive to give these synthesized types a more descriptive name in Rust.

    To interact with the data inside these types, you may need to write additional C++ functions to extract the data.

    include_cpp! {
        #include "input.h"
        safety!(unsafe_ffi)
        generate!("prepare")
        generate!("drink")
        // Give the opaque template Tea<Tapioca> a descriptive name in Rust
        concrete!("Tea<Tapioca>", Boba)
    }
    
    fn main() {
        let nicer_than_it_sounds: cxx::UniquePtr<ffi::Boba> = ffi::prepare();
        ffi::drink(&nicer_than_it_sounds);
    }
  5. Manage C++ safety with the `safety!` macro

    main

    By default, all functions generated by autocxx are marked as unsafe. This means you must wrap calls to C++ functions in unsafe blocks in your Rust code. This ensures that the developer is explicitly acknowledging the potential for C++ to violate Rust's invariants.

    If you want to treat the generated C++ calls as safe (removing the requirement for unsafe blocks), you can use the safety!(unsafe) directive within your include_cpp! macro invocation. Using this directive is a promise to the Rust compiler that all C++ function calls in that scope uphold the invariants expected by rustc.

    include_cpp! {
        #include "input.h"
        safety!(unsafe)
        generate!("do_math")
    }
  6. Understand `autocxx` safety and soundness vs `cxx`

    main

    While autocxx follows the general safety approach of the cxx crate, there are two key differences regarding soundness:

    1. Interface Specification: cxx requires detailed interface specification, forcing developers to think through language boundaries. autocxx automates this, which may lead to autogenerated "footguns" if not careful.
    2. Reference Conflicts: cxx makes it difficult for multiple conflicting Rust references to exist for 'trivial' (POD) data. In autocxx, conflicting Rust references can exist even for 'opaque' (non-POD) data. This is because autocxx communicates the size of opaque types to Rust so they can be allocated on the stack, whereas in cxx they are zero-sized.

    To mitigate reference issues with opaque types, explore using a C++ reference wrapper type (see examples/reference-wrappers).

  7. Understand how autocxx handles C++ special member functions

    main

    autocxx attempts to detect implicit C++ special member functions (Default Constructor, Destructor, Copy Constructor, and Move Constructor) to generate appropriate Rust wrappers.

    Important Caveats:

    • Analysis Requirements: To detect implicit members, autocxx must analyze the types of all bases and members. If any base or member is un-analyzed, autocxx will assume a public destructor exists but will not generate make_unique or other constructors.
    • Overloads: autocxx avoids using overloaded special members (e.g., those with const or volatile qualifiers) because selecting the correct one from Rust is ambiguous.
    • Inaccessible Constructors: If a C++ type has an inaccessible constructor, Rust can still instantiate it (leaking memory), but the C++ destructor will not be called, leading to resource leaks. Ensure constructors are accessible to the bindings.
  8. How nested C++ types are named in Rust

    main

    When generating bindings for nested C++ types, autocxx flattens the hierarchy into a single name using underscores.

    Specifically, a C++ type A::B is given the Rust name A_B and placed in the same module as its enclosing namespace A.

    // C++: struct Turkey { struct Duck { struct Hen { int wings; }; }; };
    
    include_cpp! {
        #include "input.h"
        safety!(unsafe_ffi)
        generate_pod!("Turkey_Duck_Hen")
    }
    
    fn main() {
        // Access via the flattened name
        let _turducken = ffi::Turkey_Duck_Hen::new().within_box();
    }
  9. Handle C++ function overloads

    main
    Because Rust does not support function overloading, autocxx generates unique identifiers for overloaded C++ functions by appending digits (e.g., func, func1, func2). To call a specific overload, you must use the version with the corresponding numeric suffix.
  10. When to use autocxx vs alternatives

    main

    Choosing the right tool depends on your codebase and requirements:

    Use CaseRecommended Tool
    Binding to C code (not C++)bindgen
    Small C++ to Rust interface (few functions/types)cxx
    You can make unrestricted changes to the C++ codecxx
    Large existing C++ codebase with arbitrary functions/typesautocxx
    Need bidirectional calls (C++ to Rust and Rust to C++)autocxx (or cxx)
  11. How autocxx handles C++ types (values, references, and pointers)

    main

    autocxx leverages the cxx crate to support various C++ type passing conventions. When interacting with C++ APIs, you can pass types by:

    • Value: POD (Plain Old Data) types can be passed freely. Non-POD types have specific constraints (see cpp_types.md).
    • Reference: C++ references (const or non-const) are mapped to Rust references.
    • Raw Pointer: C++ pointers are mapped to Rust pointers and require unsafe blocks in Rust.
    • Smart Pointers: Supports std::unique_ptr, std::shared_ptr, and std::weak_ptr.
    • Rvalue Reference: Supports move parameters.

    Key Safety Distinction:

    • References: Generally considered "trustworthy" in the autocxx/cxx model and do not necessarily require unsafe to use.
    • Pointers: Always require unsafe in Rust. This is because C++ pointers may be subject to concurrent mutation or have lifetimes that can expire unexpectedly. You must manually ensure the lifetime guarantees of the C++ object when using pointers.