Pre-implementation feedback for Qt with Rust

Hi,

I'm going to investigate to building a Rust wrapper for Qt. For those who don't about Qt it a cross-platform UI library written in C++ Qt for Desktop | App and UI Development with Qt It has lots of features in various areas.

Some work has been done for Rust already in this space:

GitHub - cyndis/qmlrs: QtQuick interface for Rust - Qt Quick 5.15.10 - This exposes a small part of Qt.
https://github.com/kitech/qt.rs - These are the most complete bindings our there. Dev on this seems to have slowed down, it needs some python scripts and such to generate the bindings and is split into several repositories.

The two biggest challanges I see with getting good Rust support are these:

  1. Signals & Slots | Qt Core 5.15.10 - I wonder if there is a good way to express this in Rust. The C++ version requires to you to use a tool to parse the C++ code and generates some new code for signal "bindings"

  2. The C++ API is built around that you sub-class widgets by using inheritance. I wonder if this would really be required in a Rust version or perhaps composition only and signals would be enough?

My plan of attack for the majority of the bindings is to do something similar to qt.rs but opt for a pure Rust solution (will likely use libclang for header parsing also but there is a crate for that already yah :slight_smile:

So if anyone has any experience with using Qt (in either C++ or some other lang) I would love to hear about suggestions on how the API could look and work.

Cheers!

7 Likes

Have you seen https://github.com/rust-qt?

I haven't! Nice :slight_smile:

Seems there is a head start then.

It seems that the biggest issues (slots & signals) hasn't been solved yet but I will ask about it. Thanks for the link!

On closer investigation this lib doesn't actually do anything yet and hasn't solved two biggest issues I outlined in my first post so still interested in hearing more feedback on it.

Here is an announcement of the lib on the internals: Better C++ interoperability - #28 by Riateche - internals - Rust Internals

1 Like

I think composition could work with macros that auto implement a lot of the inherited behavior for a class. I'm thinking it could be similar to how the QObject macro works in C++ but with wrapping instead of being called inside the class.

We have a good amount of qml, so having this support would be very helpful for increasing adoption at my company. I'd be willing to assist where necessary.

Yeah I hope that it will because that would make things easier for sure. I have spoken a bit with @Riateche (PM) about the project and I will start helping out with it. Riateche can likely detail the next steps and if you are able to help out as well that would be great :slight_smile:

Has anyone truly solved C++'s default parameter problem yet? Rust doesn't have anything like that and last time I checked this was a major problem as the constructors or function calls might expand into huge argument lists that aren't readable.

Edit: or did this change? I was a lot into Haskell lately and missed out on a lot of Rust stuff.

While I don't know if cpp_to_rust does it but one way would be to generate builders https://aturon.github.io/ownership/builders.html for those functions/methods that use it but maybe there is an easier way to solve it.

I wonder if it'd be possible to do with a generated macro. If it's possible, it would avoid the small performance penalty of running the builder.

I like Qt QML a lot and I would love to use it from rust. However, Qt is much more than the UI components. Qt contains code for async, network, sql, regex; and a lot of this is already present as part of rust std or pure rust libraries. And I guess that Signals and slots could be rewritten in a much safer way using Rust.

So I think that wrapping/using/calling Qt from rust is more about defining what is the right boundary between those, and what part of Qt can be separated and replaced by existing rust code. The problem is QObject, which is rather ubiquitous.

I looked at codegen for some builders code before.

In that case the compiler was able to remove all the temporary code of building up the data to send to the function so the resulting assembly were identical to if one would have called it manually. That being said it's hard to know if it happens in all cases but in general even if there were some slight overhead I don't think it would matter much.

I fully agree here.

I think that focusing on Core, Gui and Widgets is the first thing to do (as you say, many of the other things there are already Rust versions of so it doesn't make much sense to try to bring them over unless its for a specific way to integrate with the rest of the Qt code)

Signals/Slots is (like I wrote in my first post) likely the most challenging part to get right. I think it will require a bunch of experimentation to find the right boundary as you pointed out.

In my project, methods overloading and default parameters are mapped to a method with a trait argument, and the trait is implemented on tuples of valid argument types. So if we have method(int x, float y = 0, double z = 0) in C++, in Rust we can call it as method(1i32), method((1i32, 1f32)) and method((1i32, 1f32, 1f64)). Double parentheses are a bit ugly, but it's very easy to use.

Take a look at qml-rust. While not a Qt wrapper (it's a rust wrapper to DOtherSide, a QML wrapper for C), it does support signals and slots. That might give you some ideas on how to achieve this.

Thanks! Will do

In my experience, any non-trivial Qt project quickly reaches the point where it needs subclassing. The usual reason is event handling -- mouse and keyboard events, for example, don't send signals, so if you want to handle mouse events directly you need a subclass.

The model/view framework also requires subclassing (if you want to create your own models or views).

You could probably handle a lot of these use cases with callbacks, though.

A couple relevant links regarding signals and slots:

Reimplementing moc as a Clang plugin

Verdigris, a header-only C++ library for near-zero-cost abstractions that were historically provided by moc

I strongly suspect that, by a combination of the two (and using macros 1.1 to implement a custom #[derive]), you could do something like this:

#[derive(QtMoc)]
trait Foo {
    #[slot] fn frob(&mut self, x: u32);
    #[signal] fn was_frobbed(&self) -> u32;
}

So for example, you could have the #[derive] generate a const trait method (corresponding to the constexpr function in Verdigris for the same purpose) that generates the QMetaObject.

As this actually is a good example of using Macros 1.1 in a way that would need to add items within the item being modified, I hope you don't mind if I link to this from the relevant ticket on the Rust repo.

(Also, if done this way, supertraits could possibly take on some of the roles Qt uses inheritance for? Unsure.)

That is a a pretty cool idea indeed!

Also unsure about inheritance. I hope it's possible to make most things in Qt without the inheritance (and rely more on slots/signals + composition to have custom types but I'm not sure if it would work in practice.)