CXX-Qt - Safe interop between Rust and Qt

TLDR: Click here for "Getting Started" guide

This library provides a safe mechanism for bridging between Qt code and Rust code in a different way to typical Rust Qt bindings.

We acknowledge that Qt code and Rust code have different idioms so cannot be directly wrapped from one to another.

Instead of one-to-one bindings we use CXX to bridge between, this allows for normal Qt code and normal Rust code.

We feel this is more powerful than typical bindings as this allows us to provide a safe API and safe multi-threading between Qt and Rust.

To aid integration of Qt and Rust code we provide common Qt types for Rust which can pass across the bridge and provide ways to express common Qt idioms.

Through the use of macros and code generation as seen in the figure below, the developer describes a QObject with CXX-Qt macro annotations. Then CXX-Qt generates the C++ representation of the object and uses macro expansion to define the CXX bridge for the interop between C++ and Rust.

Overview of CXX-Qt concept

If you are new to CXX-Qt, we recommend you visit our Getting Started Guide.

To get detailed information on which features are available in CXX-Qt, see the bridge chapter. Should you be interested in a deeper dive into the concepts of CXX-Qt, take a look at the concepts chapter, which explains the concepts CXX-Qt introduces in detail.

Note: CXX-Qt is tested on CI on Linux, Windows, and macOS (all on x86_64). It should work on other platforms that Qt and Rust both support, however, these are not tested regularly.

CXX-Qt - Getting Started

In comparison to other Qt-Rust-bindings, CXX-Qt does not aim to simply expose Qt functionality to Rust, but rather to completely integrate Rust into the Qt ecosystem.

In this guide we'll go through a minimal example that uses CXX-Qt to create your own QObject in Rust and integrate it with a small QML-based GUI.

Prerequisites

This guide won't be able to explain everything to you, but it will try its best to make sure everyone can follow along. However, there are a few things you should be familiar with before reading this guide. It may be confusing otherwise!

First of all, you should be familiar with Rust. There are many great resources for learning Rust, like the book.

As CXX-Qt aims to integrate Rust into the existing Qt ecosystem, you should have basic knowledge of Qt and QML. If you're not familiar with Qt/QML yet, take a look at the Qt Getting started guide or the QML intro respectively.

CXX-Qt (as the name suggests) is built on top of CXX. You should have a basic idea of how CXX works before attempting to follow this guide. Take a look at the CXX documentation here: https://cxx.rs/

Installation

You'll need to have the following tools installed:

⚠️ It is vital that the qmake executable can be found by CXX-Qt. Under Windows you may have to manually add it to your PATH in order for this to work automatically.

To check that qmake can indeed be found, run:

$ qmake --version
QMake version 3.1
Using Qt version 6.5.1 in /usr/lib64

If you don't want to add QMAKE to your path you can always provide Cargo with the right path by using the QMAKE environment variable.
e.g.: QMAKE=/usr/bin/qmake cargo build

We unfortunately cannot list all ways to install these tools on all platforms. Please make sure you have installed the right toolchains before following this guide!

What this guide covers

During this getting started guide we'll first take a look at how CXX-Qt integrates with Qt's object system to allow the definition of QObjects in Rust. Then we'll dive straight into practice and define our first QObject in Rust. Followed by actually defining our GUI using QML.

We will show two different ways to build the project. First we will build the CXX-Qt code as a Rust executable without requiring a C++ build system. Additionally, we will show how to integrate CXX-Qt into a C++ application by building with CMake.

Note: CXX-Qt is tested on CI on Linux, Windows, and macOS (all on x86_64). It should work on other platforms that Qt and Rust both support, however, these are not tested regularly.

So, without further ado - let's Get Started

QObjects in Rust

The right tool for the right job.

If you only have a hammer, every problem looks like a nail.

Don't bring a knife to a gun fight.

There are many bits of advice like that. With CXX-Qt, we aim to make it possible to use the right tool for each of the many jobs necessary to build a modern GUI application.

So what is in our toolbox for a typical Qt application?

  • QML - A declarative, flexible, dynamically-typed, interpreted language that is purpose built to define reactive and beautiful GUI layouts and widgets with quick iteration speed.
  • C++ - The traditional back-end of Qt - A fast, low-level language with a strong type system. C++ offers a rich ecosystem, many Qt-specific libraries and bare-metal performance. The cost when using C++ is that it is slow to develop, very error-prone and can easily lead to memory-issues, which can instantly crash your application and cause security issues.

Notably absent then is a back-end language that allows us to get rid of the issues C++ has and provides us with a safe way to write fast back-end code. This of course is where Rust comes in. Whilst Rust doesn't have quite as rich of an ecosystem, it is typically faster to develop than C++, with easy dependency management, and most importantly, safe memory access. Therefore it is an ideal candidate to replace C++ for writing the back-end business-logic code that feeds the GUI with data.

However, C++ as well as QML still have their place in Qt applications. For that reason Rust, C++, and QML should all be able to be used to complement each other. CXX-Qt aims to make it easy to integrate all three languages with each other, through the use of Qt's meta object system and CXX.

As CXX-Qt is largely an expansion on top of CXX, you should make yourself familiar with CXX first. You can read their excellent documentation here:

https://cxx.rs/

CXX already provides idiomatic interaction between C++ and Rust, from Rust, as well as from C++. CXX-Qt builds on this foundation, to allow you to define Qt-specific concepts in Rust.

These concepts include:

  • Custom QObject classes
    • Properties
    • Invokables/Slots
    • Signals
  • Enums

As with CXX, to use these features you mark a Rust module with an attribute macro (#[cxx_qt::bridge]). Inside this bridge, you then describe the bi-directional interface between your C++/Qt and Rust code.

CXX-Qt will then expand this Rust bridge into two separate parts:

  • C++ files that define QObjects, enums etc. and expose them to Qts meta-object-system.
  • The Rust code which provides the Rust implementation of the described structures, as well as interfaces to any C++/Qt constructs you declared.

Overview of CXX-Qt module generation

Rust structs as QObjects

Similar to CXX, CXX-Qt allows you to expose Rust structs as a new type to C++. However, CXX-Qt expands this feature to allow you to create a new QObject subclass that is backed by a Rust struct. In comparison to a normal opaque CXX class, the mapping between the QObject subclass and the Rust struct is not 1:1!

The QObject subclass it its own type in Rust, as well as in C++. When such a QObject is instantiated, it will always also construct an instance of the Rust struct. The QObject can then refer to the underlying Rust struct for property access. Any behavior of this QObject subclass will also be defined in Rust, e.g. using the #[qinvokable] attribute. The Rust implementation also has access to the underlying Rust struct to modify any Rust data. In comparison to most CXX types, the outer QObject class and the inner Rust struct will remain two distinct types!

But enough theory for now, lets jump in and write our first CXX-Qt module.

Our first CXX-Qt module

As with all things Rust, we'll want to create a cargo project, run the following command inside the tutorial folder to initialize the Rust part of the project.

$ cargo new cxx-qt-tutorial
$ cd cxx-qt-tutorial

If you want to skip building with Cargo and try building with CMake directly you can add the --lib option here. That will make it easier to create a static library in Rust and use CMake to link this into a C++ executable. We'll discuss details of this later, when we integrate our Rust project with CMake

Building with Cargo is easier to start with, so if in doubt, try building with Cargo first.

As outlined in the previous section, to use CXX-Qt, we'll create a Rust module within this crate. This Rust module will then serve as our interface between Qt and Rust. First, in the src/main.rs, we tell Cargo about the module we're about to create:

pub mod cxxqt_object;

Now, we need to create a file rust/src/cxxqt_object.rs for that module. It will include our #[cxx_qt::bridge] that allows us to interact with Qt concepts.

This is a lot to take in, so let's go one step at a time.


/// The bridge definition for our QObject
#[cxx_qt::bridge]
pub mod qobject {

    unsafe extern "C++" {
        include!("cxx-qt-lib/qstring.h");
        /// An alias to the QString type
        type QString = cxx_qt_lib::QString;
    }

    unsafe extern "RustQt" {
        // The QObject definition
        // We tell CXX-Qt that we want a QObject class with the name MyObject
        // based on the Rust struct MyObjectRust.
        #[qobject]
        #[qml_element]
        #[qproperty(i32, number)]
        #[qproperty(QString, string)]
        type MyObject = super::MyObjectRust;
    }

    unsafe extern "RustQt" {
        // Declare the invokable methods we want to expose on the QObject
        #[qinvokable]
        fn increment_number(self: Pin<&mut MyObject>);

        #[qinvokable]
        fn say_hi(self: &MyObject, string: &QString, number: i32);
    }
}

use core::pin::Pin;
use cxx_qt_lib::QString;

/// The Rust struct for the QObject
#[derive(Default)]
pub struct MyObjectRust {
    number: i32,
    string: QString,
}

impl qobject::MyObject {
    /// Increment the number Q_PROPERTY
    pub fn increment_number(self: Pin<&mut Self>) {
        let previous = *self.number();
        self.set_number(previous + 1);
    }

    /// Print a log message with the given string and number
    pub fn say_hi(&self, string: &QString, number: i32) {
        println!("Hi from Rust! String is '{string}' and number is {number}");
    }
}

CXX-Qt bridge module

Starting with the module definition:


/// The bridge definition for our QObject
#[cxx_qt::bridge]
pub mod qobject {
    // ...
}

A #[cxx_qt::bridge] is the same as a #[cxx::bridge] and you can use all features of CXX in it. Additionally, a #[cxx_qt::bridge] gives you a few more features that allow you to create QObjects from Rust or declare existing QObjects for access from Rust.

extern "RustQt"

Like extern "Rust" and extern "C++" in CXX, CXX-Qt provides extern "RustQt" and extern "C++Qt".

These extern blocks instruct CXX-Qt to where the implementation of our interface lives. Anything that is marked as extern "RustQt" is implemented in Rust and will be exposed to C++. Conversely anything inside extern "C++Qt" is implemented in C++ and will be exposed to Rust.

QObject struct

First we will create a new QObject subclass. As we want to implement it in Rust, we need to place our interface inside extern "RustQt".

To create a new QObject subclass that will be defined in Rust, use a type-alias and mark it with #[qobject]. In our case the new class will be named MyObject and will be backed by a Rust struct named MyObjectRust.

    unsafe extern "RustQt" {
        // The QObject definition
        // We tell CXX-Qt that we want a QObject class with the name MyObject
        // based on the Rust struct MyObjectRust.
        #[qobject]
        #[qml_element]
        #[qproperty(i32, number)]
        #[qproperty(QString, string)]
        type MyObject = super::MyObjectRust;
    }

The Rust struct must be defined outside the bridge module and is therefore referred to using super::. This just needs to be a normal Rust struct and can contain any kind of field, even Rust-only types that are not compatible with CXX.

Unless we want to use CXX-Qt's Constructor feature we just need to ensure that this struct implements Rusts Default trait In this case we just use #[derive(Default)] on the struct.

#[derive(Default)]
pub struct MyObjectRust {
    number: i32,
    string: QString,
}

Now the #[qobject] macro will take care of creating a new QObject subclass named MyObject. Every instance of that class will also include an instance of the MyObjectRust struct that the MyObject class will defer to for any data or behavior.

To automatically export this new class to QML, we mark it with the #[qml_element] attribute. This is the same as specifying QML_ELEMENT in C++. This takes the place of the qt_add_qml_module CMake function (because that doesn't work with CXX-Qt's build system).

The #[qproperty] attribute will create a Q_PROPERTY for the given type and field name. CXX-Qt will then:

  1. Create the Q_PROPERTY on the QObject type.
  2. Create a NOTIFY signal for when the property changes.
  3. Generate getters and setters that use the underlying Rust fields and emit the NOTIFY signal on changes.

In this case the newly created QObject subclass will have two properties: number and string. CXX-Qt expects a field for each property to exist in the underlying Rust struct. For names that contain multiple words, like my_number, CXX-Qt will automatically rename the field from snake_case to camelCase to fit with C++/QML naming conventions (e.g. myNumber).

Types

Please note that any fields exposed as #[qproperty] must have types that CXX can translate to C++ types. In our case that means:

  • #[qpoperty(i32, number)] -> Q_PROPERTY(::std::int32_t number ...)
  • #[qproperty(QString, string) -> Q_PROPERTY(QString string ...)

For i32, CXX-Qt already knows how to translate it. A QString however is unknown to CXX. Luckily, the cxx_qt_lib crate already wraps many Qt types for us. We can just include them in the bridge like any other CXX type:

    unsafe extern "C++" {
        include!("cxx-qt-lib/qstring.h");
        /// An alias to the QString type
        type QString = cxx_qt_lib::QString;
    }

For more details on the available types, see the Qt types page.


CXX-Qt will then automatically generate a new QObject subclass called MyObject and expose it as an extern "C++" opaque type back to Rust. In our case, this means we can refer to the C++ QObject as qobject::MyObject, as it is defined inside the mod qobject.

This type can be used like any other CXX opaque type.

Invokables

Additionally, CXX-Qt allows us to add functionality to this QObject by referring to the type as the self type of functions in an extern "RustQt" block.

    unsafe extern "RustQt" {
        // Declare the invokable methods we want to expose on the QObject
        #[qinvokable]
        fn increment_number(self: Pin<&mut MyObject>);

        #[qinvokable]
        fn say_hi(self: &MyObject, string: &QString, number: i32);
    }

This works the same as exposing any other member function with CXX in an extern "Rust" block. Additionally CXX-Qt understands the #[qinvokable] attribute and marks the member function as Q_INVOKABLE. This means they can be called from QML.

These functions then need to be implemented outside the bridge using impl qobject::MyObject.

impl qobject::MyObject {
    /// Increment the number Q_PROPERTY
    pub fn increment_number(self: Pin<&mut Self>) {
        let previous = *self.number();
        self.set_number(previous + 1);
    }

    /// Print a log message with the given string and number
    pub fn say_hi(&self, string: &QString, number: i32) {
        println!("Hi from Rust! String is '{string}' and number is {number}");
    }
}

This setup is a bit unusual, as the type qobject::MyObject is actually defined in C++. However, it is still possible to add member functions to it in Rust and then expose them back to C++. This is the usual workflow for QObjects in CXX-Qt. CXX-Qt will define the QObject class itself in C++, but defer to Rust for any behavior.

Note that we recommend calling the bridge module qobject instead of the CXX-typical ffi. This way accessing the C++ QObject outside the bridge becomes a natural qobject::MyObject instead of ffi::MyObject.

Feel free to choose any module name you like though.

Also do not forget to import everything required for the invokable implementation.

use core::pin::Pin;
use cxx_qt_lib::QString;

In our case, we define two new functions:

  • increment_number
    • Increments the number of the MyObject.
    • The name will be converted to incrementNumber in C++.
  • say_hello
    • Prints a provided number and string.
    • The name will be converted to sayHello in C++.

Because we are implementing on the qobject::MyObject type instead of the MyObject type, self here is the C++ QObject that is generated from our MyObject struct. As this type is a CXX Opaque type, we can't actually instantiate it. Our Qt code will take care of this. Also, we can't move the QObject, which is why it is behind a Rust Pin.

CXX-Qt will generate getters and setters for all properties of our struct. That's where the number() and set_number() functions used by increment_number() come from. For more details on what you can do with the QObject from Rust and what functions CXX-Qt will generate for you, take a look at the QObject page.

And that's it. We've defined our first QObject subclass in Rust. That wasn't so hard, was it?

Now let's get to using it in Qt.

Creating our QML GUI

As noted in the QObjects in Rust chapter, we always want to use "the right tool for the right job". For a small modern GUI in Qt, that definitely means using QML. It's powerful, flexible, declarative, and allows us to iterate very quickly.

So let's add a main.qml file in a qml folder:

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12

// This must match the uri and version
// specified in the qml_module in the build.rs script.
import com.kdab.cxx_qt.demo 1.0

Window {
    height: 480
    title: qsTr("Hello World")
    visible: true
    width: 640

    MyObject {
        id: myObject
        number: 1
        string: qsTr("My String with my number: %1").arg(myObject.number)
    }

    Column {
        anchors.fill: parent
        anchors.margins: 10
        spacing: 10

        Label {
            text: qsTr("Number: %1").arg(myObject.number)
        }

        Label {
            text: qsTr("String: %1").arg(myObject.string)
        }

        Button {
            text: qsTr("Increment Number")

            onClicked: myObject.incrementNumber()
        }

        Button {
            text: qsTr("Say Hi!")

            onClicked: myObject.sayHi(myObject.string, myObject.number)
        }
    }
}

If you're not familiar with QML, take a look at the Qt QML intro. We of course also recommend our QML Intro Training.

This code will create a pretty simple GUI that consists of two Labels and two Buttons. The important part here is the use of the MyObject type. As you can see, the class we defined earlier is now usable in QML.

As it is just another QObject subclass, it can be used in Qt's property binding system, as is done with the myObject.string, which is bound to myObject.number.

The labels then simply display the data defined in the MyObject class. We can use the two buttons to interact with the MyObject instance. As you can see here, CXX-Qt has converted the snake_case of the function names to camelCase - incrementNumber and sayHi. This way the MyObject doesn't seem at all out of place in QML.

It is again important to emphasize here that MyObject is just another QObject subclass and can be used just like any other QObject subclass. The only difference being that any invokable functions are defined in Rust, instead of C++. For QML, this doesn't make a difference though.

Now that we have some application code, let's get this project building and running

Building with Cargo

In this example, we will demonstrate how to build the cxxqt_object.rs as well as any QML files using the Rust build system. Cargo will do the entire build, including linking to Qt, just like a typical Rust application.

Note that the folder structure of this example is different to the CMake tutorial. The CMake example uses a rust folder where the Rust part of the project resides in. In this setup we'll stick with a standard Cargo folder layout with just the added qml folder next to the src folder.

The complete example code is available in examples/cargo_without_cmake in the CXX-Qt repository.

If you don't want to use Cargo, and only want to use CMake to build your project, skip ahead to the next section.

Using a Cargo based setup is easier though, so if in doubt, try building with Cargo first.

Cargo setup

Add the dependencies to the Cargo.toml file. We'll need cxx, cxx-qt, cxx-qt-lib and cxx-qt-build:

[package]
name = "qml-minimal-no-cmake"
version = "0.1.0"
authors = [
  "Andrew Hayzen <andrew.hayzen@kdab.com>",
  "Be Wilson <be.wilson@kdab.com>",
  "Gerhard de Clercq <gerhard.declercq@kdab.com>",
  "Leon Matthes <leon.matthes@kdab.com>"
]
edition = "2021"
license = "MIT OR Apache-2.0"

[dependencies]
cxx = "1.0.95"
cxx-qt = "0.6"
cxx-qt-lib = "0.6"

[build-dependencies]
# The link_qt_object_files feature is required for statically linking Qt 6.
cxx-qt-build = { version = "0.6", features = [ "link_qt_object_files" ] }

Now we'll add a build.rs script next to the Cargo.toml file.

use cxx_qt_build::{CxxQtBuilder, QmlModule};

fn main() {
    CxxQtBuilder::new()
        // Link Qt's Network library
        // - Qt Core is always linked
        // - Qt Gui is linked by enabling the qt_gui Cargo feature (default).
        // - Qt Qml is linked by enabling the qt_qml Cargo feature (default).
        // - Qt Qml requires linking Qt Network on macOS
        .qt_module("Network")
        .qml_module(QmlModule {
            uri: "com.kdab.cxx_qt.demo",
            rust_files: &["src/cxxqt_object.rs"],
            qml_files: &["qml/main.qml"],
            ..Default::default()
        })
        .build();
}

This is what generates and compiles the C++ code for our MyObject class at build time. It will also link Qt to our Rust binary.

Every Rust source file that uses the #[cxx_qt::bridge] macro needs to be included in this script. In our case, this is only the src/cxxqt_object.rs file.

This is also where the QML module is defined with a QML uri and version. The files and resources in the module are then exposed in the same way as the qt_add_qml_module CMake function.

Refer to the CxxQtBuilder and cc::Build documentation for further details.

Rust executable

In src/main.rs, first import the cxxqt_object module and some types we will need to run our Qt application:

pub mod cxxqt_object;

use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl};

Define the main function that will be called when the executable starts. This works just like starting a QML application in C++:

  • Create a QGuiApplication
  • Create a QQmlApplicationEngine
  • Set the QML file path to the engine
  • Run the application
fn main() {
    // Create the application and engine
    let mut app = QGuiApplication::new();
    let mut engine = QQmlApplicationEngine::new();

    // Load the QML path into the engine
    if let Some(engine) = engine.as_mut() {
        engine.load(&QUrl::from("qrc:/qt/qml/com/kdab/cxx_qt/demo/qml/main.qml"));
    }

    // Start the app
    if let Some(app) = app.as_mut() {
        app.exec();
    }
}

To build and run the application, use cargo run.

Note that in order for CXX-Qt to work, the qmake executable must be located. This is because CXX-Qt relies on qmake to locate the necessary Qt libraries and header files on your system.

cxx-qt will find qmake in the following order:

  • Look for an environment variable QMAKE that should have the path to qmake.
    e.g.: QMAKE=/usr/bin/qmake cargo run
  • Use qmake from the PATH. If multiple qmake exists in PATH, environment variable QT_VERSION_MAJOR will control the selected one.

To check which version of Qt will be used with qmake, you can use the qmake -query command. This will display information about the Qt installation, including the version number and installation path.

Check CxxQtBuilder for more information

If this fails for any reason, take a look at the examples/cargo-without-cmake folder in the CXX-Qt repository, which contains the complete example code.

If you have cloned the CXX-Qt repository, you can run this example from within the repository using:

cargo run -p qml-minimal-no-cmake

You should now see the two Labels that display the state of our MyObject, as well as the two buttons to call our two Rust functions.

Success 🥳

For further reading, you can take a look at the bridge chapter which goes into detail about all features that CXX-Qt exposes to new QObject subclasses. As well as the Concepts chapter, which explains the concepts underlying CXX-Qt.

In the next, optional chapter, we will show how to build the same QML application with CMake.

Building with CMake

In this example, we will demonstrate how to integrate CXX-Qt code into a C++ application using CMake. Cargo builds the CXX-Qt code as a static library, then CMake links it into a C++ executable.

If you don't want to use CMake, and only want to use Cargo to build your project, refer to the previous section.

We'll first want to modify our project structure to separate the different parts of our project.

tutorial
  - cpp
  - qml
  - rust

Move the rust project into the rust folder. Pull out the qml folder back to the top level.

C++ executable

To start our QML application, we'll need a small main.cpp file with an ordinary main function. Puts this in a cpp folder to clearly separate the C++ and Rust code:

#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>

int
main(int argc, char* argv[])
{
  QGuiApplication app(argc, argv);

  QQmlApplicationEngine engine;

  const QUrl url(
    QStringLiteral("qrc:/qt/qml/com/kdab/cxx_qt/demo/qml/main.qml"));
  QObject::connect(
    &engine,
    &QQmlApplicationEngine::objectCreated,
    &app,
    [url](QObject* obj, const QUrl& objUrl) {
      if (!obj && url == objUrl)
        QCoreApplication::exit(-1);
    },
    Qt::QueuedConnection);

  engine.load(url);

  return app.exec();
}

You can add as much C++ code as you want in addition to this.

Using Rust QObjects in C++

For every #[cxx_qt::bridge] that we define in Rust, CXX-Qt will generate a corresponding C++ header file. They will always be in the cxx-qt-gen/ include path and use the snake_case naming convention. The name of the header file will be the name of the Rust module of your #[cxx_qt::bridge], followed by .cxxqt.h. So in our case: #include cxx-qt-gen/qobject.cxxqt.h

Note that the cxx_file_stem option can be specified in the bridge macro to choose the file name.

Including the generated header allows accessing the MyObject C++ class, just like any other C++ class. Inherit from it, connect signals and slots to it, put it in a QVector, do whatever you want with it. That's the power of CXX-Qt.

Cargo setup

Before we can get started on building Qt with CMake, we first need to make our Cargo build ready for it. If you've generated your project with the cargo new --lib or cargo init --lib [folder] command, your Cargo.toml should look something like this:

[package]
name = "qml-minimal"
version = "0.1.0"
edition = "2021"

[dependencies]

We'll have to do multiple things:

  • Instruct cargo to create a static library
  • Add cxx, cxx-qt, as well as cxx-qt-lib as dependencies
  • Add cxx-qt-build as a build dependency

If you've already followed the Cargo setup, most of this should already be done. Make sure to change the crate-type to "staticlib" though!

In the end, your Cargo.toml should look similar to this.

[package]
name = "qml-minimal"
version = "0.1.0"
authors = [
  "Andrew Hayzen <andrew.hayzen@kdab.com>",
  "Gerhard de Clercq <gerhard.declercq@kdab.com>",
  "Leon Matthes <leon.matthes@kdab.com>"
]
edition = "2021"
license = "MIT OR Apache-2.0"

# This will instruct Cargo to create a static
# library which CMake can link against
[lib]
crate-type = ["staticlib"]

[dependencies]
cxx = "1.0.95"
cxx-qt = "0.6"
cxx-qt-lib = "0.6"

# cxx-qt-build generates C++ code from the `#[cxx_qt::bridge]` module
# and compiles it together with the Rust static library
[build-dependencies]
cxx-qt-build = "0.6"

[features]
# This feature must be enabled for `cargo test` when linking Qt 6 statically.
link_qt_object_files = [ "cxx-qt-build/link_qt_object_files" ]

We'll then also need to add a script named build.rs next to the Cargo.toml:

If you've already followed the Cargo build tutorial, simply modify the existing build.rs file.

use cxx_qt_build::{CxxQtBuilder, QmlModule};

fn main() {
    CxxQtBuilder::new()
        .qml_module(QmlModule {
            uri: "com.kdab.cxx_qt.demo",
            rust_files: &["src/cxxqt_object.rs"],
            qml_files: &["../qml/main.qml"],
            ..Default::default()
        })
        .build();
}

This is what generates and compiles the C++ code for our MyObject class at build time.

Every Rust source file that uses the #[cxx_qt::bridge] macro need to be included in this script. In our case, this is only the src/cxxqt_object.rs file.

This is also where the QML module is defined with a QML uri and version. The files and resources in the module are then exposed in the same way as the qt_add_qml_module CMake function.

Note that in order for CXX-Qt to work, the qmake executable must be located. This is because CXX-Qt relies on qmake to locate the necessary Qt libraries and header files on your system.

This will be done in the CMakeLists.txt file by setting the QMAKE environment variable from CMake. This ensures that CMake and Cargo use the same Qt binaries.

We'll also need to remove the src/main.rs and replace it with a src/lib.rs file. This file only needs to include a single line:

pub mod cxxqt_object;

This simply ensures that our rust module is included in our library.

Feel free to add additional rust modules in your library as well.

CMake setup

Now add a CMakeLists.txt file in the root of your project folder. Start the CMakeLists.txt file like any other C++ project using Qt. For this example, we are supporting both Qt5 and Qt6 with CMake:

cmake_minimum_required(VERSION 3.24)

project(example_qml_minimal)
set(APP_NAME ${PROJECT_NAME})

# Rust always links against non-debug Windows runtime on *-msvc targets
# Note it is best to set this on the command line to ensure all targets are consistent
# https://github.com/corrosion-rs/corrosion/blob/master/doc/src/common_issues.md#linking-debug-cc-libraries-into-rust-fails-on-windows-msvc-targets
# https://github.com/rust-lang/rust/issues/39016
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
  set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
endif()

set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if(NOT USE_QT5)
    find_package(Qt6 COMPONENTS Core Gui Qml QuickControls2 QmlImportScanner)
endif()
if(NOT Qt6_FOUND)
    find_package(Qt5 5.15 COMPONENTS Core Gui Qml QuickControls2 QmlImportScanner REQUIRED)
endif()

Locate Corrosion, a tool for integrating Rust libraries into CMake. If Corrosion is not installed, automatically download it:

find_package(Corrosion QUIET)
if(NOT Corrosion_FOUND)
    include(FetchContent)
    FetchContent_Declare(
        Corrosion
        GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
        GIT_TAG v0.4.2
    )

    FetchContent_MakeAvailable(Corrosion)
endif()

To ensure that cxx-qt-build uses the same version of Qt as your CMake targets, use the Qt CMake target to locate the qmake executable. Then, pass qmake executable path to build.rs with the environment variable QMAKE using corrosion_set_env_vars.

# The path to the qmake executable path needs to be passed to the Rust
# library's build script to ensure it uses the same installation of Qt as CMake.
get_target_property(QMAKE Qt::qmake IMPORTED_LOCATION)

Use Corrosion to create a CMake library target for the Rust library. CXX-Qt requires a few more steps beyond using a typical Rust library with Corrosion:

set(CRATE qml-minimal)
# Corrosion creates a CMake target with the same name as the crate.
corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml CRATES ${CRATE})

# The Rust library's build script needs to be told where to output the
# generated headers so CMake can find them. To do this, tell Corrosion
# to set the CXXQT_EXPORT_DIR environment variable when calling `cargo build`.
# Also, set the QMAKE environment variable to ensure the Rust library uses
# the same installation of Qt as CMake.
set(CXXQT_EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cxxqt")
corrosion_set_env_vars(${CRATE}
    "CXXQT_EXPORT_DIR=${CXXQT_EXPORT_DIR}"
    "QMAKE=${QMAKE}"
)

# Create an INTERFACE library target to link libraries to and add include paths.
# Linking this to both the application and the tests avoids having to setup
# the include paths and linked libraries for both of those.
add_library(${APP_NAME}_lib INTERFACE)

# Include the headers generated by the Rust library's build script. Each
# crate gets its own subdirectory under CXXQT_EXPORT_DIR. This allows you
# to include headers generated by multiple crates without risk of one crate
# overwriting another's files.
target_include_directories(${APP_NAME}_lib INTERFACE "${CXXQT_EXPORT_DIR}/${CRATE}")

target_link_libraries(${APP_NAME}_lib INTERFACE
    # WHOLE_ARCHIVE is needed for the generated QML plugin to register on startup,
    # otherwise the linker will discard the static variables that initialize it.
    "$<LINK_LIBRARY:WHOLE_ARCHIVE,${CRATE}-static>"
    Qt::Core
    Qt::Gui
    Qt::Qml
    Qt::QuickControls2
)

Finally, create the CMake executable target and link it to the Rust library:

# Define the executable with the C++ source
add_executable(${APP_NAME} cpp/main.cpp)

# Link to the Rust library
target_link_libraries(${APP_NAME} PRIVATE ${APP_NAME}_lib)

# If we are using a statically linked Qt then we need to import any qml plugins
qt_import_qml_plugins(${APP_NAME})

Your project should now have a structure similar to this:

$ tree -I target/ -I tests
.
├── CMakeLists.txt
├── cpp
│   └── main.cpp
├── qml
│   └── main.qml
└── rust
    ├── build.rs
    ├── Cargo.toml
    └── src
        ├── cxxqt_object.rs
        └── lib.rs

5 directories, 7 files

Build the project like any other CMake project:

$ cmake -S . -B build
$ cmake --build build

If this fails for any reason, take a look at the examples/qml_minimal folder, which contains the complete example code.

This should now configure and compile our project. If this was successful, you can now run our little project.

$ build/examples/qml_minimal/example_qml_minimal

You should now see the two Labels that display the state of our MyObject, as well as the two buttons to call our two Rust functions.

Windows with MSVC

If you're building CXX-Qt on Windows using MSVC generator, you need to ensure that set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") is set in CMake (or use the -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDLL flag) when building with the Debug configuration. This flag is necessary to ensure that the correct C Runtime Library is used. Then you can build using cmake --build build --config Debug.

This issue is caused by a bug in the cc crate (as described in https://github.com/rust-lang/cc-rs/pull/717), which has not been merged yet. Specifically, the problem is that cc generated code always links to the MultiThreaded runtime, even when building in Debug mode. We hope that this step won't be necessary in the future, once the cc crate fix is merged and released.

Core Concepts

CXX-Qt uses CXX for bridging between C++ and Rust in a safe way.

The main purpose of CXX-Qt is to expose Qt's extensions to the C++ language to CXX.

Note that the Rust QObject of a constructed Qt object is owned by the C++ side of the bridge representing it. So when the C++ object is destroyed the Rust object will be destroyed.

Build Systems

CXX-Qt can be integrated into existing CMake projects or built with only cargo. The getting started guide provides documentation on how to setup your project:

CXX-Qt could work with any C++ build system so long as the QMAKE and CXXQT_EXPORT_DIR environment variables are set before calling Cargo, as documented in CMake integration. However, using C++ build systems besides CMake with CXX-Qt is untested.

CxxQtBuilder

With both build systems a build script (build.rs) file needs to be used, so that CXX-Qt knows which files to look for bridges and to build a Qt C++ library for linking later.

See CxxQtBuilder documentation for more details.

QML Modules

When using QML with CXX-Qt QML modules can be output. This allows for attributes such as #[qml_element] to register the QObject with the QML type system without any C++ code.

See QmlModule documentation for more details.

The generated QObject

One of the key features of CXX-Qt is the ability to create your own QObjects from Rust. This is what the #[qobject] macro is for. This page serves to document the details of what is generated and how to interact with the generated QObject from Rust.

The #[qobject] macro generates a QObject for a type alias to a Rust struct. Whilst this QObject is a C++ type, CXX-Qt will automatically wrap it as a CXX Opaque Type.

If the bridge module is named qobject, then the C++ type can be reached via qobject::T

Anatomy

Any QObject generated by CXX-Qt is just a C++ QObject subclass that owns an instance of the Rust struct.

By default the instance of the Rust struct is constructed using the Default trait. If the Rust struct cannot implement Default, providing a custom constructor with the Constructor trait is required.

The C++ object will defer any state to the Rust struct, and is therefore only a thin wrapper.

See extern "RustQt" for details on implementing properties, invokables, and signals.

See nested objects for referencing another QObject.

C++ context

When implementing methods in the C++ context (eg for invokables) these need to be implemented on the type defined in the bridge.

For example if the bridge module was called qobject and the type was called T an impl block would be written as impl qobject::T { ... }.

Methods from traits, such as Threading, are available in the C++ context

From a C++ context the Rust context can be reach by using methods on the CxxQtType trait

Rust context

The only requirement for the Rust struct is that it has a Default or that the QObject implements cxx_qt::Constructor.

Otherwise the Rust struct can be used in the same way as any normal Rust struct.

Types

CXX-Qt supports most types supported by CXX. These can be used in properties, invokables, and signals.

cxx-qt-lib Types

The cxx-qt-lib crate provides CXX bindings for common Qt types.

Use the cxx-qt-lib Docs to explore the available types.

Container Types

The cxx-qt-lib crate has containers types, such as QSet<T>.

To use these define a templated type in the CXX bridge, but note that the type name must be QSet_T as this needs to match the name in C++ code.

So for QSet<i32> the type name should be QSet_i32.

    unsafe extern "C++" {
        include!("cxx-qt-lib/qset.h");
        type QSet_i32 = cxx_qt_lib::QSet<i32>;
    }

To use key-value based container types such as QHash<K, V> an intermediate type on the Rust side is defined to implement a trait on the key-value combination.

As with other container types, the type name must be QHash_K_V as this needs to match the name in the C++ code.

So for QHash<QString, QVariant>, define an intermediate type called QHashPair_QString_QVariant. Then the type name QHash_QString_QVariant is used to match the C++ side.

    unsafe extern "C++" {
        include!("cxx-qt-lib/qhash.h");
        type QHash_QString_QVariant = cxx_qt_lib::QHash<cxx_qt_lib::QHashPair_QString_QVariant>;
    }

Note that type alias such as QVariantMap can be used by using the matching type in Rust such as QMap<QString, QVariant>.

Defining a Custom Type

Any types that are valid CXX types should be usable with CXX-Qt as well.

Note that the same rules apply as CXX, so a type must be trivial to pass by value. If they are opaque, references or pointers must be used.

For examples of how to wrap Qt objects, explore the cxx-qt-lib source code.

Using a Custom Type with Containers or QVariant

To use a custom type with containers find the trait that the container uses, eg for QSet<T> there is a QSetElement trait and for QHash<K, V> there is a QHashPair trait.

Implement the trait for your custom type and then you can use the containers as described above.

To use a custom type with QVariant implement the QVariantValue trait for your custom type, as seen below, then it can be used as normal.

impl cxx_qt_lib::QVariantValue for ffi::CustomStruct {
    fn can_convert(variant: &cxx_qt_lib::QVariant) -> bool {
        ffi::qvariant_can_convert_custom_type(variant)
    }

    fn construct(value: &Self) -> cxx_qt_lib::QVariant {
        ffi::qvariant_construct_custom_type(value)
    }

    fn value_or_default(variant: &cxx_qt_lib::QVariant) -> Self {
        ffi::qvariant_value_or_default_custom_type(variant)
    }
}

A full example of implementing a custom struct with QVariant is shown in the qml_features types example.

Also any custom types or alias in C++ should be registered with Qt using qRegisterMetaType<T>("TYPE") to ensure that they work with QML.

Nested Objects

Rust Qt objects can be nested as properties or parameters of each other.

A nested object is referred to by using a pointer to its QObject representation.

First define a type within an extern block for your bridge as normal.

    extern "RustQt" {
        #[qobject]
        #[qml_element]
        #[qproperty(i32, counter)]
        type InnerObject = super::InnerObjectRust;
    }

This can then be used as a property, invokable parameter, or signal parameter by using *mut T. As seen in the example below which nests InnerObject into OuterObject.

The C++ CXX type needs to be used as the T type not the Rust struct

To reach mutable invokables and property setters of the nested object *mut T needs to be converted to Pin<&mut T>.

#[cxx_qt::bridge(cxx_file_stem = "nested_qobjects")]
pub mod qobject {
    extern "RustQt" {
        #[qobject]
        #[qml_element]
        #[qproperty(i32, counter)]
        type InnerObject = super::InnerObjectRust;
    }

    extern "RustQt" {
        /// A signal showing how to refer to another QObject as an argument
        ///
        /// # Safety
        ///
        /// Due to a raw pointer this is considered unsafe in CXX
        #[qsignal]
        unsafe fn called(self: Pin<&mut InnerObject>, inner: *mut InnerObject);
    }

    extern "RustQt" {
        #[qobject]
        #[qml_element]
        #[qproperty(*mut InnerObject, inner)]
        type OuterObject = super::OuterObjectRust;

        /// A signal showing how to refer to another QObject as an argument
        ///
        /// # Safety
        ///
        /// Due to a raw pointer this is considered unsafe in CXX
        #[qsignal]
        unsafe fn called(self: Pin<&mut OuterObject>, inner: *mut InnerObject);
    }

    unsafe extern "RustQt" {
        /// Print the count of the given inner QObject
        #[qinvokable]
        unsafe fn print_count(self: Pin<&mut OuterObject>, inner: *mut InnerObject);

        /// Reset the counter of the inner QObject stored in the Q_PROPERTY
        #[qinvokable]
        fn reset(self: Pin<&mut OuterObject>);
    }

    impl cxx_qt::Constructor<()> for OuterObject {}
}

use core::pin::Pin;

/// The inner QObject
#[derive(Default)]
pub struct InnerObjectRust {
    counter: i32,
}

/// The outer QObject which has a Q_PROPERTY pointing to the inner QObject
pub struct OuterObjectRust {
    inner: *mut qobject::InnerObject,
}

impl Default for OuterObjectRust {
    fn default() -> Self {
        Self {
            inner: std::ptr::null_mut(),
        }
    }
}

impl qobject::OuterObject {
    /// Print the count of the given inner QObject
    ///
    /// # Safety
    ///
    /// As we deref a pointer in a public method this needs to be marked as unsafe
    pub unsafe fn print_count(self: Pin<&mut Self>, inner: *mut qobject::InnerObject) {
        if let Some(inner) = inner.as_ref() {
            println!("Inner object's counter property: {}", inner.counter());
        }

        self.called(inner);
    }

    /// Reset the counter of the inner QObject stored in the Q_PROPERTY
    pub fn reset(self: Pin<&mut Self>) {
        // We need to convert the *mut T to a Pin<&mut T> so that we can reach the methods
        if let Some(inner) = unsafe { self.inner().as_mut() } {
            let pinned_inner = unsafe { Pin::new_unchecked(inner) };
            // Now pinned inner can be used as normal
            pinned_inner.set_counter(10);
        }

        // Retrieve *mut T
        let inner = *self.inner();
        unsafe { self.called(inner) };
    }
}

impl cxx_qt::Initialize for qobject::OuterObject {
    /// Initialize the QObject, creating a connection from one signal to another
    fn initialize(self: core::pin::Pin<&mut Self>) {
        // Example of connecting a signal from one QObject to another QObject
        // this causes OuterObject::Called to trigger InnerObject::Called
        self.on_called(|qobject, obj| {
            // We need to convert the *mut T to a Pin<&mut T> so that we can reach the methods
            if let Some(inner) = unsafe { qobject.inner().as_mut() } {
                let pinned_inner = unsafe { Pin::new_unchecked(inner) };
                // Now pinned inner can be used as normal
                unsafe {
                    pinned_inner.called(obj);
                }
            }
        })
        .release();
    }
}

Inheritance

Some Qt APIs require you to override certain methods from an abstract base class, for example QAbstractItemModel.

To support creating such subclasses directly from within Rust, CXX-Qt provides you with multiple helpers.

Some superclasses may require special parameters for construction. This can be achieved by using a custom constructor.

Accessing base class methods

To access the methods of a base class in Rust, use the #[inherit] macro. It can be placed in front of a function in a extern "RustQt" block in a #[cxx_qt::bridge].

    extern "RustQt" {
        #[qobject]
        #[base = "QAbstractListModel"]
        #[qml_element]
        #[qproperty(State, state)]
        type CustomBaseClass = super::CustomBaseClassRust;
    }

    // Create Rust bindings for C++ functions of the base class (QAbstractItemModel)
    extern "RustQt" {
        /// Inherited beginInsertRows from the base class
        #[inherit]
        unsafe fn begin_insert_rows(
            self: Pin<&mut CustomBaseClass>,
            parent: &QModelIndex,
            first: i32,
            last: i32,
        );
        /// Inherited endInsertRows from the base class
        #[inherit]
        unsafe fn end_insert_rows(self: Pin<&mut CustomBaseClass>);

        /// Inherited beginRemoveRows from the base class
        #[inherit]
        unsafe fn begin_remove_rows(
            self: Pin<&mut CustomBaseClass>,
            parent: &QModelIndex,
            first: i32,
            last: i32,
        );
        /// Inherited endRemoveRows from the base class
        #[inherit]
        unsafe fn end_remove_rows(self: Pin<&mut CustomBaseClass>);

        /// Inherited beginResetModel from the base class
        #[inherit]
        unsafe fn begin_reset_model(self: Pin<&mut CustomBaseClass>);
        /// Inherited endResetModel from the base class
        #[inherit]
        unsafe fn end_reset_model(self: Pin<&mut CustomBaseClass>);
    }

    unsafe extern "RustQt" {
        /// Clear the rows in the QAbstractListModel
        #[qinvokable]
        pub fn clear(self: Pin<&mut CustomBaseClass>);
    }
impl qobject::CustomBaseClass {
    /// Clear the rows in the QAbstractListModel
    pub fn clear(mut self: Pin<&mut Self>) {
        unsafe {
            self.as_mut().begin_reset_model();
            self.as_mut().rust_mut().id = 0;
            self.as_mut().rust_mut().vector.clear();
            self.as_mut().end_reset_model();
        }
    }
}

Full example

This code implements a QAbstractListModel subclass. For this, the clear method implemented in Rust needs to call beginResetModel and related methods from the base class, which are made accessible by using #[inherit]. See the Qt docs for more details on the specific subclassing requirements.

Methods in a extern "RustQt" block similar to CXX can be tagged with an #[inherit] attribute, with the same restrictions regarding which types can be used. Additionally, the self type must be either self: Pin<&mut qobject::T> or self: &qobject::T, where qobject::T must refer to a QObject marked with #[qobject] in the #[cxx_qt::bridge]

The declared methods will be case-converted as in other CXX-Qt APIs. To explicitly declare the C++ method name, use the #[cxx_name = "myFunctionName"] attribute.

#[inherit] can also be used on signals that exist on the base class in an extern RustQt block

Overriding base class methods

CXX-Qt allows invokables to be generated with the C++ modifiers necessary to implement inheritance. This way methods can be overridden, declared as virtual or final.

C++ keywordCXX-Qt attribute
override#[cxx_override]
virtual#[cxx_virtual]
final#[cxx_final]

The example below overrides the data method inherited from the QAbstractListModel.

    extern "RustQt" {
        #[qobject]
        #[base = "QAbstractListModel"]
        #[qml_element]
        #[qproperty(State, state)]
        type CustomBaseClass = super::CustomBaseClassRust;
    }

    unsafe extern "RustQt" {
        #[qinvokable]
        #[cxx_override]
        fn data(self: &CustomBaseClass, index: &QModelIndex, role: i32) -> QVariant;
    }
impl qobject::CustomBaseClass {
    /// Retrieve the data for a given index and role
    pub fn data(&self, index: &QModelIndex, role: i32) -> QVariant {
        let role = qobject::Roles { repr: role };
        if let Some((id, value)) = self.vector.get(index.row() as usize) {
            return match role {
                qobject::Roles::Id => QVariant::from(id),
                qobject::Roles::Value => QVariant::from(value),
                _ => QVariant::default(),
            };
        }

        QVariant::default()
    }
}

Full example

When a method is overridden using cxx_override, the base class version of the method can be accessed by using #[inherit] in combination with the #[cxx_name] attribute. In this case the base class version of the function must get a different name because Rust can't have two functions with the same name on one type.

Example:

    extern "RustQt" {
        #[qobject]
        #[base = "QAbstractListModel"]
        #[qml_element]
        #[qproperty(State, state)]
        type CustomBaseClass = super::CustomBaseClassRust;
    }

    unsafe extern "RustQt" {
        /// Inherited canFetchMore from the base class
        #[cxx_name = "canFetchMore"]
        #[inherit]
        fn base_can_fetch_more(self: &CustomBaseClass, parent: &QModelIndex) -> bool;

        /// Inherited index from the base class
        #[inherit]
        fn index(
            self: &CustomBaseClass,
            row: i32,
            column: i32,
            parent: &QModelIndex,
        ) -> QModelIndex;
    }

    unsafe extern "RustQt" {
        /// Return whether the base class can fetch more
        // Example of overriding a C++ virtual method and calling the base class implementation.
        #[qinvokable]
        #[cxx_override]
        fn can_fetch_more(self: &CustomBaseClass, parent: &QModelIndex) -> bool;
    }
impl qobject::CustomBaseClass {
    /// Return whether the base class can fetch more
    // Example of overriding a C++ virtual method and calling the base class implementation.
    pub fn can_fetch_more(&self, parent: &QModelIndex) -> bool {
        self.base_can_fetch_more(parent)
    }
}

Full example

The bridge module reference

  • extern "RustQt" - exposing Rust types to Qt as QObject, Q_SIGNAL, Q_PROPERTY etc
  • extern "C++Qt" - binding Qt features and types to Rust, such as QObject, Q_SIGNAL etc
  • Shared types - shared enums between Rust and Qt, such as Q_ENUM, Q_ENUM_NS etc
  • Attributes - working with namespaces, giving functions different names
  • Traits - traits related to a CXX-Qt QObject

The #[cxx_qt::bridge] macro functions very similarly to #[cxx::bridge]. This macro needs to be written above a Rust module definition.

This Rust module will then function like a normal CXX bridge, whilst also supporting the additional features added by CXX-Qt. Refer to the the CXX documentation for details on how to describe the language boundary.

Don't forget to add the Rust source file to the CxxQtBuilder in your build.rs script. For instructions, see the Getting Started guide.

The #[cxx_qt::bridge] macro supports two options in it's attribute

cxx_file_stem

By default, the name of the generated C++ header file will be the name of the module, followed by .cxxqt.h (and .cxx.h for CXX files).

This can cause issues as the module is normally called ffi or qobject so collisions would occur.

The cxx_file_stem option allow a file name to be specified to avoid collisions.

#[cxx_qt::bridge(cxx_file_stem = "types")]
pub mod ffi {

Currently, cxx-qt-gen writes all generated header files into a single folder. Therefore you need to be careful to not produce two header files with the same filename.

We want to use the name of the Rust source file that the macro is located in (the same as CXX). However this requires inspection APIs from proc_macro::Span which is currently a nightly feature.

extern "RustQt"

#[cxx_qt::bridge]
mod ffi {
    extern "RustQt" {

    }
}

The extern "RustQt" section of a CXX bridge declares Rust types and signatures to be made available to Qt and C++.

The CXX code generator uses your extern "Rust" section(s) to produce a C++ header file containing the corresponding C++ declarations. The generated header has a file name matching the module ident or the cxx_file_stem field in the #[cxx_qt::bridge] attribute and with a .cxxqt.h file extension.

A bridge module may contain zero or more extern "RustQt" blocks.

This complements the extern "Rust" CXX section but allows for declaring Qt specific features on C++ types.

QObjects

Types specified with a #[qobject] attribute are generated in C++ as a QObject.

The left side of the type specifies the C++ generated type and name, when referring to the C++ context this should be used. The right side of the type specifies which Rust type provides the inner implementation of the type (for example fields ).

#[cxx_qt::bridge]
mod ffi {
    extern "RustQt" {
        #[qobject]
        type MyObject = super::MyObjectRust;
    }
}

#[derive(Default)]
struct MyObjectRust;

QML Attributes

QObjects can be registered as a QML type directly at build time by using the #[qml_element] attribute.

    unsafe extern "RustQt" {
        // The QObject definition
        // We tell CXX-Qt that we want a QObject class with the name MyObject
        // based on the Rust struct MyObjectRust.
        #[qobject]
        #[qml_element]
        #[qproperty(i32, number)]
        #[qproperty(QString, string)]
        type MyObject = super::MyObjectRust;
    }

Additionally, you can configure the QML registration with these attributes:

  • qml_name: Use a different type name for QML.
  • qml_uncreatable: Mark the type as uncreatable from QML. It may still be returned by C++/Rust code.
  • qml_singleton: An instance of the QObject will be instantiated as a singleton in QML.

The Rust file must be included within a QML module in the build.rs file

base attribute

Use the base attribute to specify a C++ class that the C++ QObject will inherit from. The base class must inherit from QObject (directly or indirectly). If you do not specify a base attribute, it will inherit directly from QObject.

    extern "RustQt" {
        #[qobject]
        #[base = "QAbstractListModel"]
        #[qml_element]
        #[qproperty(State, state)]
        type CustomBaseClass = super::CustomBaseClassRust;
    }

Use the CXX include! macro to include the appropriate C++ header for the base class:

    unsafe extern "C++" {
        include!(<QtCore/QAbstractListModel>);
    }

For more information on inheritance and how to override methods see the Inheritance & Overriding page.

Full Example

Traits

The Default trait needs to be implemented for the #[qobject] marked struct either by hand or by using the derive macro #[derive(Default)]. Or the cxx_qt::Constructor trait needs to be implemented for the type.

For further documentation see the traits page.

Properties

The #[qproperty(TYPE, NAME, ...)] attribute can be specified on a #[qobject] marked type to expose a Q_PROPERTY on the generated QObject.

        #[qobject]
        #[qml_element]
        #[qproperty(bool, connected)]
        #[qproperty(QUrl, connected_url)]
        #[qproperty(QUrl, previous_connected_url)]
        #[qproperty(QString, status_message)]
        type RustProperties = super::RustPropertiesRust;

The type and name of the

/// A QObject which has Q_PROPERTYs
pub struct RustPropertiesRust {
    /// A connected Q_PROPERTY
    connected: bool,

    /// A connected_url Q_PROPERTY
    pub(crate) connected_url: QUrl,

    /// A previous_connected_url Q_PROPERTY
    previous_connected_url: QUrl,

    /// A status_message Q_PROPERTY
    status_message: QString,
}

For every #[qproperty], CXX-Qt will generate setters and getters, as well as a "changed" signal.

On the C++ side:

  • setter: set<Property>
  • getter: get<Property>
  • changed: <Property>Changed

On the Rust side:

  • setter: set_<Property>
  • getter: <Property>
  • changed: <Property>_changed

Also the generated Rust methods for signals

  • connect: connect_<Property>_changed
  • on: on_<Property>_changed

Where <Property> is the name of the property.

These setters and getters assure that the changed signal is emitted every time the property is edited.

Note that in the future it will be possible to specify custom getters and setters

Methods

Any signature with a self parameter is interpreted as a Rust method and exposed to C++ method for the given type. The type much be either a shared reference self: &T or a pinned mutable reference self: Pin<&mut T>, where T is the QObject type.

    unsafe extern "RustQt" {
        /// C++ only method which returns the red value
        fn red_value(self: &RustInvokables) -> f32;
    }

Implementations of the method are then written as normal on the C++ type outside the bridge.

impl qobject::RustInvokables {
    /// C++ only method which returns the red value
    pub fn red_value(&self) -> f32 {
        self.red
    }
}

Note how this uses impl qobject::T rather than impl T where qobject is the bridge module name.

Invokables

The #[qinvokable] attribute can be specified on signatures to expose them as a Q_INVOKABLE in C++.

    unsafe extern "RustQt" {
        /// Immutable invokable method that returns the QColor
        #[qinvokable]
        fn load_color(self: &RustInvokables) -> Result<QColor>;

        /// Mutable invokable method that stores a color
        #[qinvokable]
        fn store_color(self: Pin<&mut RustInvokables>, red: f32, green: f32, blue: f32);

        /// Mutable invokable method that stores a color with an enum
        #[qinvokable]
        fn store_color_with_enum(self: Pin<&mut RustInvokables>, color: Color);

        /// Mutable invokable method with no parameters that resets the color
        #[qinvokable]
        fn reset(self: Pin<&mut RustInvokables>);
    }

Implementations then have no difference to non invokable methods.

impl qobject::RustInvokables {
    /// Immutable invokable method that returns the QColor
    pub fn load_color(&self) -> Result<QColor, i32> {
        Ok(self.as_qcolor())
    }

    /// Mutable invokable method that stores a color
    pub fn store_color(self: Pin<&mut Self>, red: f32, green: f32, blue: f32) {
        self.store_helper(red, green, blue);
    }

    /// QENUMS!
    pub fn store_color_with_enum(self: Pin<&mut Self>, color: qobject::Color) {
        use qobject::Color;
        let (r, g, b) = match color {
            Color::Red => (1.0, 0.0, 0.0),
            Color::Green => (0.0, 1.0, 0.0),
            Color::Blue => (0.0, 0.0, 1.0),
            _ => (0.0, 0.0, 0.0),
        };
        self.store_helper(r, g, b);
    }

    /// Mutable invokable method with no parameters that resets the color
    pub fn reset(self: Pin<&mut Self>) {
        self.store_helper(0.0, 0.4667, 0.7843);
    }
}

Inheritance

Methods or signals that already exist on the base class of an object can be accessed via the #[inherit] attribute.

For documentation see the inheritance page.

Specifiers

Generated methods can have C++ specifiers necessary to implement inheritance.

C++ keywordCXX-Qt attribute
override#[cxx_override]
virtual#[cxx_virtual]
final#[cxx_final]

These are specified as an attribute on the method signature.

    unsafe extern "RustQt" {
        #[qinvokable]
        #[cxx_override]
        fn data(self: &CustomBaseClass, index: &QModelIndex, role: i32) -> QVariant;
    }

Signals

The qsignal attribute is used in an extern "RustQt" block to define signals for the a QObject.

    unsafe extern "RustQt" {
        /// A Q_SIGNAL emitted when a connection occurs
        #[qsignal]
        fn connected(self: Pin<&mut RustSignals>, url: &QUrl);

        /// A Q_SIGNAL emitted when a disconnect occurs
        #[qsignal]
        fn disconnected(self: Pin<&mut RustSignals>);

        /// A Q_SIGNAL emitted when an error occurs
        #[qsignal]
        fn error(self: Pin<&mut RustSignals>, message: QString);
    }

For every function signature in the extern block, CXX-Qt will generate a signal on the corresponding QObject. If the function has parameters, they will become the parameters for the corresponding signal.

If a signal is defined on the base class of the QObject then #[inherit] can be used to indicate to CXX-Qt that the Q_SIGNAL does not need to be created in C++.

A full example can be found in the qml features.

Note that #[cxx_name = "..."] can also be used on a signal to declare a different name in C++ to Rust

Note using pub(self) as the visibility of the signal allows for declaring private signals

Connecting to a signal

For every signal defined in the enum, two methods are generated.

  1. on_<signal_name>
  2. connect_<signal_name>

The on_<signal_name> method takes a handler function as the parameter, which will be called when the signal is emitted. That handler function's first argument is the qobject and the remaining arguments are the signal parameters.

The connect_<signal_name> function additionally takes the Qt connection type as a parameter.

Note that by using the #[inherit] macro on a signal, connections can be made to property changes using the signal name <property>Changed with no parameters.

                    let connections = [
                        qobject.as_mut().on_connected(|_, url| {
                            println!("Connected: {}", url);
                        }),
                        qobject.as_mut().on_disconnected(|_| {
                            println!("Disconnected");
                        }),
                        // Demonstration of connecting with a different connection type
                        qobject.as_mut().connect_error(
                            |_, message| {
                                println!("Error: {}", message);
                            },
                            ConnectionType::QueuedConnection,
                        ),
                    ];
                    qobject.as_mut().rust_mut().connections = Some(connections);

Each connection returns a QMetaObject::Connection which is used to manage the connection.

Note that the QMetaObjectConnection returned by CXX-Qt behaves a bit different from the Qt C++ implementation.

When the QMetaObjectConnection is dropped, it automatically disconnects the connection, similar to how a C++ std::unique_ptr or Rusts Box behaves. If you don't want to store the QMetaObjectConnection, call release, which will drop the object without disconnecting. In this case, it is no longer possible to disconnect later.

                // By making connections None, we trigger a drop on the connections
                // this then causes disconnections
                qobject.as_mut().rust_mut().connections = None;

Emitting a signal

Call the function signature defined in the extern "RustQt" block to emit the signal.

Note that these are defined on the generated QObject qobject::T, so can be called from any mutable #[qinvokable].

The function will immediately emit the signal. Depending on the connection type, the connected slots will be called either immediately or from the event loop (See the different connection types). To queue the call until the next cycle of the Qt event loop, you can use the CxxQtThread.

Inheritance

If a signal is defined on the base class of the QObject then the #[inherit] attribute can be used to indicate to CXX-Qt that the Q_SIGNAL does not need to be created in C++.

    unsafe extern "RustQt" {
        /// Inherit the DataChanged signal from the QAbstractListModel base class
        #[inherit]
        #[qsignal]
        fn data_changed(
            self: Pin<&mut CustomBaseClass>,
            top_left: &QModelIndex,
            bottom_right: &QModelIndex,
            roles: &QVector_i32,
        );
    }

extern "C++Qt"

#[cxx_qt::bridge]
mod ffi {
    extern "C++Qt" {

    }
}

The extern "C++Qt" section of a CXX-Qt bridge declares Qt types and signatures to be made available to Rust, and gives the paths of the headers which contain the corresponding Qt declarations.

A bridge module may contain zero or more extern "C++Qt" blocks.

This complements the extern "C++" CXX section but allows for declaring Qt specific features on C++ types.

QObjects

Types defined in C++ that are made available to Rust, but only behind an indirection.

This is the same as CXX Opaque C++ types.

#[cxx_qt::bridge]
mod ffi {
    extern "C++Qt" {
        include!(<QtWidgets/QPushButton>);
        type QPushButton;
    }
}

Methods

Methods can be specified on the Qt type in the same way as extern "RustQt" blocks.

This is the same as CXX Functions and member functions.

#[cxx_qt::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("cxx-qt-lib/qstring.h");
        type QString = cxx_qt_lib::QString;
    }

    extern "C++Qt" {
        include!(<QtWidgets/QPushButton>);
        type QPushButton;

        fn text(self: &QPushButton) -> QString;
        fn setText(self: Pin<&mut QPushButton>, text: &QString);
    }
}

Signals

Signals can be specified on the Qt type in the same way as extern "RustQt" blocks.

#[cxx_qt::bridge]
mod ffi {
    extern "C++Qt" {
        include!(<QtWidgets/QPushButton>);
        type QPushButton;

        #[qsignal]
        fn clicked(self: Pin<&mut QPushButton>, checked: bool);
    }
}

This then causes CXX-Qt to generate Rust methods to connect to the #[qsignal] with a closure, in the same way as a #[qsignal] in a extern "RustQt" block.

Note using pub(self) as the visibility of the signal allows for declaring private signals

Shared types

#[qenum] - Support for Q_ENUM and Q_ENUM_NS

Qt allows exposing enums to Qt's meta-object system, and thereby QML, with a set of macros:

  • Q_ENUM is used to expose an enum that is a member of a QObject
  • Q_ENUM_NS is used to expose an enum that is in a namespace to the meta-object system.

CXX-Qt has support for both of these macros through the #[qenum] attribute.

QObject class enum (Q_ENUM)

CXX-Qt relies on CXX to expose enums from Rust to C++ and vice-versa. However, CXX only supports free enums that are not defined as part of a class. CXX-Qt doesn't change this, it only additionally exposes the enum as part of a QObject type to the meta-object system. So any #[qenum] in CXX-Qt is available as both a normal shared CXX enum, as well as a Q_ENUM inside the associated QObject.

To expose a shared CXX enum as a Q_ENUM inside a QObject class, add the #[qenum(...)] attribute to the enum definition in CXX. The argument to #[qenum(...)] must be the name of a #[qobject] that is defined in a extern "RustQt" block.

It is currently not possible to add a #[qenum(...)] to any extern "C++Qt" QObjects or a QObject that is defined in another #[cxx_qt::bridge].

Example:

#[cxx_qt::bridge(cxx_file_stem="custom_base_class")]
pub mod qobject {
    #[qenum(CustomBaseClass)]
    /// State of the CustomBaseClass list model
    enum State {
        /// Another item is being added in the background
        Running,
        /// No items are being added in the background
        Idle,
    }

    extern "RustQt" {
        #[qobject]
        #[base = "QAbstractListModel"]
        #[qml_element]
        #[qproperty(State, state)]
        type CustomBaseClass = super::CustomBaseClassRust;
    }
}

Registering the enum with QML

Note that Qt provides access to enum variants through the name of the class it is registered with, not the enum name itself. A side-effect of this behavior is that the enum itself doesn't have to be registered with QML. Only the QObject class has to be registered. In the previous example, the #[qml_element] attribute on the #[qobject] takes care of the registration.

Usage from QML:

    BusyIndicator {
        anchors {
            right: scrollView.right
            bottom: scrollView.bottom
            margins: 15
        }
        running: customBaseClass.state === CustomBaseClass.Running
    }

Namespaced enum (Q_ENUM_NS)

If there is no class that the enum should be associated with, Qt still allows exposing the enum to the meta-object system, as long as it is inside a namespace.

If there is a namespace associated with a shared CXX enum simply add the #[qenum] attribute and CXX-Qt will expose it using Q_ENUM_NS.

Note that the namespace doesn't have to be specified on the enum directly, the enum can inherit the namespace from the surrounding bridge. This follows normal CXX namespacing rules.

Example:

#[cxx_qt::bridge]
pub mod qobject {
    #[qenum]
    #[namespace = "Colors"]
    /// An enum of colors
    enum Color {
        /// Red
        Red,
        /// Green
        Green,
        /// Blue
        Blue,
    }
}

Unfortunately, an important Qt limitation also applies to CXX-Qt. Namely, for any given namespace, there must be at most one bridge that exposes #[qenum] enums through that namespace. One bridge may expose enums through multiple namespaces however.

Registering the enum with QML

Whilst Q_ENUM_NS creates the appropriate meta-objects, it doesn't add them to QML automatically. Like with Q_ENUM, access to the enum variants also doesn't happen through the enum directly, but rather the surrounding namespace.

Therefore, the namespace must be registered with the meta-object system and then exposed to QML. CXX-Qt automatically registers the namespace of a namespaced #[qenum] with the meta-object system.

Registration with QML can then be done by placing a qnamespace!("...") macro inside the bridge that defines the namespaced #[qenum] and adding a #[qml_element] attribute.

#[cxx_qt::bridge]
pub mod qobject {
    #[qml_element]
    qnamespace!("Colors");

    #[qenum]
    #[namespace = "Colors"]
    /// An enum of colors
    enum Color {
        /// Red
        Red,
        /// Green
        Green,
        /// Blue
        Blue,
    }
}

Usage from QML:

            ToolButton {
                text: qsTr("Red")
                onClicked: rustInvokables.storeColorWithEnum(Colors.Red);
            }

Attributes

namespace

The C++ namespace which to emit extern "RustQt" items and the namespace to find extern "C++Qt" items.

An item will inherit the namespace specified on it's surrounding extern block if any, otherwise the namespace specified with the top level cxx_qt::bridge attribute, if any, will be used.

#[cxx_qt::bridge(cxx_file_stem = "threading_website", namespace = "cxx_qt::website")]
pub mod qobject {

Note that #[namespace = "..."] may not work on all items, we hope to improve this in future this support in the future.

cxx_name and rust_name

The #[cxx_name = "..."] attribute replaces the name that C++ should use for this item.

The #[rust_name = "..."] attribute replaces the name that Rust should use for this item.

Note that #[cxx_name = "..."] and #[rust_name = "..."] may not work on all items, we hope to improve this in future this support in the future.

If no #[cxx_name = "..."] or #[rust_name = "..."] is specified, CXX-Qt will perform an automatic conversion to function names as specified in the table below.

RustC++
extern "C++Qt"-camelCase
extern "RustQt"-camelCase

Note that in some cases snake_case conversions may occur for generated functions in Rust (eg on_<signal>).

Note that this table may change to the following conversions in the future.

RustC++
extern "C++Qt"snake_case-
extern "RustQt"-camelCase

Traits

Traits can be implemented (or in some cases negated) inside the #[cxx_qt::bridge] in a similar way to explicit shim trait impls in CXX.

Except instead of the T being a generic it is the struct the trait is implemented for. This is because some of the traits themselves require generics.

impl UniquePtr<A> {} // explicit CXX trait implementation of UniquePtr for A

impl cxx_qt::Trait for A {} // explicit CXX-Qt trait implementation of Trait for A
  • CxxQtType - trait to reach the Rust implementation of a QObject
  • Constructor - custom constructor
  • Initialize - execute Rust code when the object is constructed
  • Locking - marker trait whether locking is enabled
  • Threading - marker trait whether CXX-Qt threading should be enabled