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.
-
the work is planned here. ↩