# qmetaobject-rs, exposing Rust types to QML by building the QMetaObject at compile time

> woboq/qmetaobject-rs is a Rust framework for writing Qt and QML applications, where a custom derive macro generates the QMetaObject at compile time so a plain Rust struct with annotated fields becomes a QObject with properties, signals and slots that QML can use directly.

**woboq/qmetaobject-rs** — Integrate Qml and Rust by building the QMetaObject at compile time.

- Repository: https://github.com/woboq/qmetaobject-rs
- Stars: 736 · Forks: 92
- Language: Rust
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/woboq-qmetaobject-rs

## What the framework does and the objectives behind it

qmetaobject-rs describes itself as a framework empowering everyone to create Qt and QML applications with Rust, and it does so by building QMetaObjects at compile time, registering QML types optionally via exposing QQmlExtensionPlugins, and providing idiomatic wrappers.

The README lists four objectives, and they are the design constraints that explain every decision in the crate.

The first is a Rust procedural macro, a custom derive, to generate a QMetaObject at compile time. The second is bindings for the main Qt types using the cpp! macro from the cpp crate. The third is that users of this crate should not require to type any line of C++ or use another build system beyond cargo. The fourth is performance, stated as avoiding any unnecessary conversion or heap allocation.

The third objective is the one that matters most in practice. Integrating Rust with Qt usually means a C++ shim, a binding generator, and a build system that has to know about both. Eliminating all three is a real simplification, and it is what makes the approach usable by someone who knows Rust and QML but not the Qt C++ ecosystem.

The fourth objective explains the design of the macro system. A QObject exposed to QML needs properties that notify, slots that can be invoked and signals that can be connected, and building that in a way that does not allocate on every call means the metadata has to be generated rather than constructed at runtime. Generating it at compile time also means the compiler can check it, which is where most of the safety of the approach comes from.

The features list covers the object types you can create, inheriting from QObject, QQuickItem, QAbstractListModel, QQmlExtensionPlugin and others, exporting properties, signals and methods. There is also support for the QGadget derive, described as the same as the C++ Q_GADACT macro, partial scene graph support, and the ability to create a Qt plugin.

The requirement is Qt 5.8 or newer. The licence is MIT, the last push was on 2026-04-13, and the published releases are v0.2.6 and v0.2.7 from February 2022, with v0.0.5 from 2019 before them. The gap between the release dates and the recent push is worth noting when judging activity.

## What the derive macro actually looks like

The overview example in the README is the clearest possible statement of the design, and it is worth reading closely because every line maps onto a Qt concept.

```rust
use cstr::cstr;
use qmetaobject::prelude::*;

// The `QObject` custom derive macro allows to expose a class to Qt and QML
#[derive(QObject, Default)]
struct Greeter {
    // Specify the base class with the qt_base_class macro
    base: qt_base_class!(trait QObject),
    // Declare `name` as a property usable from Qt
    name: qt_property!(QString; NOTIFY name_changed),
    // Declare a signal
    name_changed: qt_signal!(),
    // And even a slot
    compute_greetings: qt_method!(fn compute_greetings(&self, verb: String) -> QString {
        format!("{} {}", verb, self.name.to_string()).into()
    })
}
```

A struct that inherits nothing in Rust becomes a class in Qt. The base field with the qt_base_class macro names the C++ class it derives from, and the derive macro generates the corresponding C++ shim at build time.

The property declaration carries its type and the name of the notification signal, in the same form Qt uses, so a property change notifies the QML side. The signal declaration is its own field. The method declaration takes a Rust function body and makes it a slot callable from QML, with the return value converted into the Qt type.

The registration and the engine are then ordinary Rust:

```rust
fn main() {
    // Register the `Greeter` struct to QML
    qml_register_type::<Greeter>(cstr!("Greeter"), 1, 0, cstr!("Greeter"));
    // Create a QML engine from rust
    let mut engine = QmlEngine::new();
    // (Here the QML code is inline, but one can also load from a file)
    engine.load_data(r#"
        import QtQuick 2.6
        import QtQuick.Window 2.0
        // Import our Rust classes
        import Greeter 1.0

        Window {
            visible: true
            // Instantiate the rust struct
            Greeter {
                id: greeter;
                // Set a property
                name: "World"
            }
            Text {
                anchors.centerIn: parent
                // Call a method
                text: greeter.compute_greetings("hello")
            }
        }
    "#.into());
    engine.exec();
}
```

The QML side imports the Rust type as an ordinary module, instantiates it as a QML object, sets a property and calls a method. Nothing about the type is special to QML, which is the whole point: the generated QMetaObject is the standard interface, so every existing QML tool and documentation applies to it.

## The cargo features and what they switch on

The crate uses cargo features to keep the default footprint small, and the README explains each one.

The log feature is enabled by default, and its reason is worth reading because it is a common problem in embedded and cross-language work. By default, Qt's logging system is not initialized, so messages from QML's console.log go nowhere. The feature enables integration with the log crate, the Rust logging facade, and to activate it you call qmetaobject::log::init_qt_to_rust() as early as possible in main, and then set up env_logger or any other logging backend yourself. The division of responsibility is the sensible one: the crate routes Qt messages into Rust, the application decides where they go.

The chrono_qdatetime feature enables interoperability between QDate and QTime and Rust's chrono types. It is disabled by default, which is the right default for a crate that is otherwise Qt-only, and it is the feature that makes a Qt date picker and a Rust date type the same thing rather than two things that need converting at every boundary.

The webengine feature enables QtWebEngine functionality, with an example in the repository, and is also disabled by default. It is the heaviest of the three by a wide margin, since it brings an entire browser engine into the dependency tree, and it is correctly off unless you need it.

The pattern across the three is that the defaults are the common case and the extras are opt-in, with the reason given for each. That is a sign of a crate whose maintainer has thought about what a user actually has installed.

The README also explains what to do when a wrapper for a Qt C++ API is missing. The answer is that you can always access C++ directly from your Rust code using the cpp! macro, and the maintainer says they strive to increase the coverage of the wrapped API and invites feature requests and pull requests. So the escape hatch is documented rather than hidden, which means the wrapper set is a convenience rather than a boundary.

## Writing your own Qt wrappers and the wider ecosystem

The README includes a tutorial for adding Rust wrappers for the Qt C++ API, and it is the section that shows how the project is put together under the hood.

The first step is to add qttypes as a dependency with the features for the Qt modules you need, pointing at recent versions on crates.io, and to refer to the qttypes crate documentation for the full list of supported modules. The README also gives the alternative for when you absolutely need the latest unreleased changes, which is a path dependency or a git dependency pointing at the repository rather than a version.

The second step is to add cpp as a dependency and cpp_build as a build dependency, with a pointer to the cpp crate documentation for the up-to-date instructions. The third step is to copy the build script from the qmetaobject directory, which runs cpp_build against your package using the environment provided by the qttypes build script.

That third step is the important one for understanding the architecture. The Qt type wrappers live in a separate crate, qttypes, and the generated code depends on them, so adding a module to the surface means enabling a feature rather than writing the bindings yourself. The build script handles the C++ compilation and the environment that the other build script sets up.

The overall structure is therefore a family of crates rather than one: qmetaobject is the macro and runtime side, qttypes is the Qt type wrapper set with a build script, and the cpp and cpp_build crates do the C++ interop. The project name and the blog post at woboq.com about building a QMetaObject from Rust point at the same idea, which is that the Qt object metadata is the interface and it can be generated rather than hand-written.

The judgement on the project is that it is an unusually complete answer to a problem that usually ends up as a pile of glue. The requirements are strict, Qt 5.8 or newer with a matching toolchain and a C++ build, and the releases are old, so for a project starting today you would be building against a crate whose last published version is from 2022 even though the repository is being touched. The examples and the blog post are the fastest way to judge whether the approach fits your problem, and the documentation of a missing wrapper as a supported case is a good sign about the project's maturity.

## Conclusion

Adopt qmetaobject-rs if you want to write a QML user interface while keeping your application logic in Rust, and you are prepared to depend on the Qt libraries at build time. Skip it if your interface is a conventional native widget application, where the C++ and Rust integrations are more mature. Verify first by building the example against a Qt 5.8 or newer installation, and by confirming that the Qt version on your target machine is the one you linked against.

## FAQ

### What is qmetaobject-rs?

It is an MIT licensed Rust framework for creating Qt and QML applications, which works by building QMetaObjects at compile time so a Rust struct can be registered as a QML type with properties, signals and slots. The documentation and the crates are published as qmetaobject, with a presentation blog post on the maintainer's site.

### What do I need to use qmetaobject-rs?

Qt 5.8 or newer, a Rust toolchain, and cargo as the only build system. The stated objectives include that users should not need to write any line of C++ or use another build system beyond cargo, and that the implementation should avoid unnecessary conversion or heap allocation.

### Which cargo features does qmetaobject-rs offer?

The log feature is enabled by default and routes Qt logging into the Rust log facade, needing a call to init_qt_to_rust early in main. The chrono_qdatetime feature, disabled by default, enables interoperability between QDate and QTime and the chrono crate. The webengine feature, also disabled by default, enables QtWebEngine functionality.

### How do I add Qt wrappers that the crate does not provide?

The README has a tutorial for making your own crate with new Qt wrappers, based on a Graph example in the repository. You add qttypes as a dependency with the features for the modules you need, add cpp and cpp_build, and copy the build script from the qmetaobject directory, which runs cpp_build using the environment set up by the qttypes build script.

### How mature is the qmetaobject-rs project?

The published releases are v0.2.6 and v0.2.7 from February 2022, with v0.0.5 from 2019 before them, while the repository was last pushed to on 2026-04-13. The package is marked private in its package metadata and is used by the author's own related crates such as qttypes.

## Sources

- [Issues](https://github.com/woboq/qmetaobject-rs/issues)
- [License: MIT](https://github.com/woboq/qmetaobject-rs/blob/master/LICENSE)
- [README](https://github.com/woboq/qmetaobject-rs/blob/master/README.md)
- [Releases](https://github.com/woboq/qmetaobject-rs/releases)
- [woboq/qmetaobject-rs on GitHub](https://github.com/woboq/qmetaobject-rs)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/woboq-qmetaobject-rs
