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