This blog shows how Qt applications can be built with Cargo. The goal is to make compiling them as simple as installing Qt and running
cargo build
The crates qrep
and mailmodel
are
examples. You can try qrep
with
cargo install qrep
qrep
qrep
is a minimal GUI for ripgrep
.
mailmodel
is a proof-of-concept mail reader.
You can get started quickly with your own Rust Qt application by
copying the folder templates/qt_quick_cargo
from the Rust
Qt Binding Generator repository.
In previous blogs we built applications with CMake. CMake is probably the most widely used tool to compile Qt software. CMake uses Cargo to build the Rust parts.
CMake is a familiar tool for Qt developers, but not for Rust
developers. For them, CMake is an unneeded hurdle. Rust Qt Binding
Generator can be used with only Cargo. To do so you use the Rust way of
building C++ code: build.rs
.
build.rs
build.rs
contains Rust code that is compiled and run by
Cargo to build an application. Here is a simple build.rs
file:
extern crate rust_qt_binding_generator;
fn main() {
// obtain the output directory from an environment variable
let out_dir = ::std::env::var("OUT_DIR").unwrap();
// create a new builder
rust_qt_binding_generator::build::Build::new(&out_dir)
// add Rust Qt bindings
.bindings("bindings.json")
// add files to compile into the program as resources
.qrc("qml.qrc")
// add some C++ code
.cpp("src/main.cpp")
// compile it to a static library
.compile("my_project");
}
This file is placed directly in your project folder. The last
command, compile("my_project")
, compiles a library called
my_project
.
Cargo.toml
build.rs
and the name of the library should be added to
Cargo.toml
:
[package]
name = "my_project"
version = "0.1.0"
build = "build.rs" # use build.rs for custom build steps
links = "my_project" # and link to the resulting library
[dependencies]
libc = "0.2"
[build-dependencies]
rust_qt_binding_generator = "0.2"
src/main.rs
main.rs
is the entry point for Rust applications. In
these applications, two things should happen:
include the generated Rust code interface.rs
.
call into the C++ code.
Qt applications have an event loop. Starting this loop requires some C++ code. We start the event loop in a C++ function. The name of the application is passed to this function.
extern crate libc;
// include the interface generated from bindings.json
pub mod interface {
include!(concat!(env!("OUT_DIR"), "/src/interface.rs"));
}
// the module with our implementation of the interface
mod implementation;
// declare the function that calls into the C++ code
extern {
fn main_cpp(app: *const ::std::os::raw::c_char);
}
fn main() {
use std::ffi::CString;
let app_name = ::std::env::args().next().unwrap();
let app_name = CString::new(app_name).unwrap();
unsafe {
// call the C++ function that starts the Qt event loop
.as_ptr());
main_cpp(app_name}
}
src/main.cpp
The template contains one C++ file. That is usually all you need. But
it’s possible to add more C++ files by adding more calls to
Build::cpp
.
This file loads the GUI from a QML file and starts the event loop.
#include "Bindings.h"
#include <QtCore/QFile>
#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>
#include <QtQml/qqml.h>
extern "C" {
int main_cpp(const char* app);
}
int main_cpp(const char* appPath) {
// create a QGuiApplication
int argc = 1;
char* argv[1] = { (char*)appPath };
QGuiApplication app(argc, argv);
// register the type that we wrote in Rust
qmlRegisterType<Simple>("RustCode", 1, 0, "Simple");
// This is QML application. We load the QML file from the file system
// or take the one embedded into the application otherwise.
QQmlApplicationEngine engine;
if (QFile("main.qml").exists()) {
.load(QUrl(QStringLiteral("main.qml")));
engine} else {
.load(QUrl(QStringLiteral("qrc:/main.qml")));
engine}
// start the event loop
return app.exec();
}
qml.qrc
Qt applications can contain resources. These are embedded files that
are accessible via qrc:
URLs. The template application
contains two resources: main.qml
and
MainForm.qml
. Other resources such as translations or
application icons can be added as well.
RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>MainForm.ui.qml</file>
<qresource>
</RCC> </
The option to build Qt applications with Cargo should please Rust programmers. I’ve ported my personal projects to build this way. Using CMake is also still supported and is recommended for C++ projects that currently use CMake and would like to introduce Rust.
This feature is new and not widely tested. Bugs can be filed here.
Comments
Post a comment