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

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.

Subclasses without a safety! policy

Where the C++ superclass has a single trivial constructor, autocxx normally implements CppPeerConstructor for your subclass. It stops doing so if you leave out the safety! directive, because then every generated function is unsafe - the peer’s constructor included - and make_peer is a safe method, so the generated body wouldn’t compile.

Subclasses still work; you write that one impl yourself, putting the unsafe block around the call:

use autocxx::prelude::*;
use autocxx::subclass::{CppPeerConstructor, CppSubclass, CppSubclassRustPeerHolder};
use cxx::UniquePtr;

impl CppPeerConstructor<ffi::MyObserverCpp> for MyObserver {
    fn make_peer(
        &mut self,
        peer_holder: CppSubclassRustPeerHolder<Self>,
    ) -> UniquePtr<ffi::MyObserverCpp> {
        unsafe { ffi::MyObserverCpp::new(peer_holder) }
    }
}

This is the same impl you would write under a safety! policy for a superclass with several constructors, or one which takes arguments - see the trait’s documentation. Nothing else about the subclass changes: the ownership constructors, the _methods trait and the casts all behave as they do above.

The peer’s new hands back the UniquePtr itself, so the body is just the call - no .within_unique_ptr() and nothing to emplace. Most constructors autocxx generates hand back a moveit::new::New instead, letting the caller put the object where it likes, including in Rust storage. That cannot be offered here: autocxx writes the peer class itself, after bindgen has run, so nothing ever measures it, and its Rust side is cxx’s zero-sized opaque type. Only C++ can allocate a peer.

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.

The superclass implements its own _methods trait too, so a function written as fn feed(d: &impl Dinosaur_methods) takes a TRex, a Diplodocus, or a C++ Dinosaur alike. (The generator can’t do this where one of the superclass’s virtual methods has no binding of its own - a private or protected one, for instance - because there would be nothing for the implementation to call.)

Older releases did not write that impl. If you filled the gap with an impl Dinosaur_methods for Dinosaur of your own, delete it when you upgrade: Rust rejects the two together with E0119, conflicting implementations.