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:

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.