Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

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.

Each numbered function carries a doc comment saying which C++ declaration it came from - C++: saw, declaration 2 of that name, counting in the order autocxx met them - so the IDE has something to show you. Read the position rather than the digits: a suffix skips any number already taken by a real function, so func3 is not necessarily the fourth func. The count is in the order autocxx met the declarations, which is usually the header’s order but not guaranteed to be - a member function template, for one, is met after every ordinary method. The same note names the C++ function behind a name Rust would not accept as an identifier, where type reaches you as type_.

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();
}

Ref-qualified methods

A method qualified with & is called just like any other method - autocxx always has an lvalue to call it on, so the qualifier makes no difference to how you use it.

C++ header:


#include <cstdint>
class Sloth {
public:
    uint32_t claws() const & { return 3; }
};

Rust:


use autocxx::prelude::*;

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

fn main() {
    let sloth = ffi::Sloth::new().within_unique_ptr();
    assert_eq!(sloth.claws(), 3);
}

A method qualified with && is a different matter: it can only be called on an object which is about to be discarded, and autocxx only ever holds a C++ object behind a reference or a smart pointer. There’s no way to express that, so no bindings are generated for such methods. The rest of the type is unaffected, and the generated code carries an explanation where the method would have been.

Member function templates

class Sloth {
public:
    template<typename T> void hang_from(T branch);
};

Calling hang_from means choosing what T is, and nothing autocxx generates can say so: a cxx::bridge names C++ functions, and the function you want here does not exist until something names the arguments which would instantiate it. There is no turbofish for a C++ template. So no bindings are generated for a member function template. The rest of the type is unaffected, and the generated code carries an explanation where the method would have been - which is the point of this, because such a member used to be simply missing, with nothing in the bindings to say it had ever been declared.

To call one, give C++ a name for the instantiation you want and bind that:

inline void hang_from_branch(Sloth& sloth, Branch branch) {
    sloth.hang_from(branch);
}

An explicit specialization written in the header is not enough on its own: C++ does provide a callable function that way, but libclang reports it outside the class rather than as a member of it, so autocxx does not reach it. The wrapper is the reliable route.

The same applies to the member functions of a class template, which are described under generic types: those are bound for an instantiation you declare instantiable!, and a member function template among them is the one kind which is not.


  1. the work is planned here. ↩