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"
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.7"
cxx-qt-lib = { version="0.7", features = ["qt_full"] }

[build-dependencies]
# The link_qt_object_files feature is required for statically linking Qt 6.
cxx-qt-build = { version = "0.7", 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 of cxx-qt-lib.
        // - Qt Qml is linked by enabling the qt_qml Cargo feature of cxx-qt-lib.
        // - 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"));
    }

    if let Some(engine) = engine.as_mut() {
        // Listen to a signal from the QML Engine
        engine
            .as_qqmlengine()
            .on_quit(|_| {
                println!("QML Quit!");
            })
            .release();
    }

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

To build and run the application, use cargo run.

📝 Note: 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.