Our first CXX-Qt module
As with all things Rust, we'll want to create a cargo project.
Run the following command inside a tutorial
folder to initialize the Rust part of the project.
$ cargo new qml_minimal
$ cd qml_minimal
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 CMakeBuilding 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)]
#[namespace = "my_object"]
type MyObject = super::MyObjectRust;
}
unsafe extern "RustQt" {
// Declare the invokable methods we want to expose on the QObject
#[qinvokable]
#[cxx_name = "incrementNumber"]
fn increment_number(self: Pin<&mut MyObject>);
#[qinvokable]
#[cxx_name = "sayHi"]
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 QObject
s from Rust or declare existing QObject
s 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)]
#[namespace = "my_object"]
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 can 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 MyObject
will include an instance of the MyObjectRust
struct that can be used to work with data defined in Rust.
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++.
We later define a QML module for our type when building with Cargo or CMake.
The #[qproperty]
attribute will create a Q_PROPERTY
for the given type and field name.
CXX-Qt will then:
- Create the
Q_PROPERTY
on theQObject
type. - Create a
NOTIFY
signal for when the property changes. - 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 allows you to rename the property in C++ with a custom cxx_name
or rust_name
. See the property documentation for more information.
Types
Please note that any fields exposed as #[qproperty]
must have types that CXX can translate to C++ types.
In our case that means:
#[qproperty(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.
With our completed QObject definition, CXX-Qt will 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 qobject::MyObject
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]
#[cxx_name = "incrementNumber"]
fn increment_number(self: Pin<&mut MyObject>);
#[qinvokable]
#[cxx_name = "sayHi"]
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 QObject
s in CXX-Qt.
CXX-Qt will define the QObject
class itself in C++, but defer to Rust for any behavior.
📝 Best Practice: We recommend calling the bridge module
qobject
instead of the CXX-typicalffi
. This way accessing the C++QObject
outside the bridge becomes a naturalqobject::MyObject
instead offfi::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++.
- Increments the number of the
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.
See the QProperty
section for details on user defined properties.
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.