Rewriting Qt in Rust
-
@SimonSchroeder said in Rewriting Qt in Rust:
One of my main gripes with Qt is that all GUI programming must be funneled through a single thread (explicitly).
Just had to comment to this one, as that made me laugh.
To be clear, this is just about the GUI classes, Qt itself does not in any way restrict you from using threads and off-loading work to worker threads. (Notice the QThreadPool class in QtCore).
Yes, indeed, you are obligated to manually route your GUI updates to the 'main' qt thread. The alternative would be massive locking infrastructure in the GUI framework or a massive amount of overhead of every function needing to move the update to the main thread for you behind the scenes.
You may not like it, but its the most commonly used solution for any generic set of (GUI) classes where the number of classes and methods is quite large.
It feels like you might just not have understood signals and QObject thread affinity if you think this is a problem, really.
Qt allows one of the most beautiful way of moving content from one thread to the other without any efforts on the side of the programmer.All you need to do is connect a queued connection between your data class and your GUI class. The
signals: void nameChanged(const QString&);
on one side and thepublic slots: void setName(const QString&)
on the other when connected will automatically be a queued connection if your data class (the emitting one) has a different thread affinity than the GUI one.
Nowhere have I seen such a simple way to allow you to introduce multi-threading to offload the hard work so it avoids blocking the GUI updating thread.Edit:
@SimonSchroeder said in Rewriting Qt in Rust:
I'm also annoyed that layout calculations are only done once the widget is shown. Even more annoying is using multiple monitors with different resolutions. I would hope that a modern UI kit would handle this properly in the background. I am even certain that it should be possible to make widget partly overlapping two screens having the right resolution/scaling on each of the two monitors.
Notice that those issues are not Qt issues, really.
They are issues in the graphics layer it builds on top of. The fact that the all graphics systems require the app to reserve a square for the window and draw into that, specifically.
Anything Qt could do would be a major hack and fail for a significant number of users.
-
@TomZ said in Rewriting Qt in Rust:
It feels like you might just not have understood signals and QObject thread affinity if you think this is a problem, really.
@SimonSchroeder understands Qt signals and thread affinity perfectly well! :) [Well I think so anyway!] His comment was what it was, i.e. Qt requires all UI accesses to be done in a single thread, which is a perfectly valid observation to make of as you will. As are your observations :)
-
@JonB said in Rewriting Qt in Rust:
Qt requires all UI accesses to be done in a single thread, which is a perfectly valid observation to make of as you will.
It is a valid observation, what is missing from that observation is what I replied with is that there is no clean alternative solution in any language or any framework known today.
This is a generic problem: how do you keep your stuff free from race-conditions if you have hundreds of classes and thousands of methods. For clarity, I'm JUST talking about the GUI stuff. Which is a small subjection of the whole.
If you find any framework of the size we are dealing with that solves it better, please share.
-
@SimonSchroeder said in Rewriting Qt in Rust:
@TomZ said in Rewriting Qt in Rust:
The main point of Qt is that its made by a company that sells APIs.
The community can always fork from Qt under the GPL and LPGL. This way you can have Qt as full open source without a company behind it. Having a company putting in any money at all into an "open source" project is a good thing. I know that in reality the dynamics are a lot more complicated.
PS: There have been "forks" from Qt that want to be drop-in replacements in one sort or another:
https://www.copperspice.com
https://github.com/woboq/verdigris
If you haven't heard of those it makes the point for reality vs. the spirit of open source. The community seems to prefer Qt from a company over those two alternative solutions.Just one thing: verdigris is not a fork at all, it's a header only library providing an alternative set of macros to define Qt objects as well as signals and slot in a way that is binary compatible with Qt, but does not require moc. You would use that with your standard Qt installation.
-
@TomZ
I just jumped to his defence. I modified my post a bit, don't want anybody to be down on anybody else!I don't know much about what other UIs/toolkits take as their approach. Purely OOI if one used, say, the Windows SDK to access a UI do you know what their requirement is if one uses threads [I do not know but am interested]?
-
When i write Rust code and go back to writing C or C++ (C*), i feel like a retard, i feel unsafe and not confident. C* are buggy languages and have design faults. A compiler should prevent you from making mistakes and if it allowed you to make mistakes, then this is a bug. I think these ancient languages should never be used in 2023. C was invented in 1972, 51 years ago and C++ in 1985, 38 years ago. Lots of things have changed in the last 20 years. The hardware have improved a lot and many things have been built back better. Yet C* are still clenching to old rules for the sake of backward compatibility, and this is the right thing to do in order not to break the existing code.
So i believe that C* should only be used to maintain legacy code, new software should be written in Rust. I see that Rust is the best tool to write C++ like software.
Another thing is learning programming for beginners. Learning Rust is very easy and the tooling for Rust is amazing. The official book is not the best and i see it can be written better for people who have never programmed before. But learning C* is a trouble for beginners. And its very easy for beginners to introduce deadly mistakes in C*. You will need a safe, fast, efficient, productive language like Rust to make learning programming easy and fun for as much people as you can. The more new good programmers you make, the more software will get written and the faster a new safer software world will grow.
So maybe yes it's a stupid idea to rewrite Qt in Rust and i no longer support this. But It would be amazing if the developers of Qt contributed in a new Rust GUI framework like Xilem.
A little more thing to add is that you will always need to build back better from time to time. I think problems in Rust will appear after like 40 years from now and you may need to change things in Rust or build a new language. At this point, a tool can be written to convert the whole existing Rust code to the new language and we may not even need to rewrite anything manually. We should always try to innovate things and not keep sticking to the old stuff.
This is my personal point of view.
-
I do come with a strong Java background and havent used a lot of C++ in the recent years. But I simply cannot follow your arguments you have posted.
@Pete-Carter said in Rewriting Qt in Rust:
When i write Rust code and go back to writing C or C++ (C*), i feel like a retard, i feel unsafe and not confident.
Well, I am out of practice with modern C++ (which means C++14/17/20) but still I don‘t feel scared going back to C++ writing code there. I feel scared when I read Rust code as IMHO it looks really ugly to me.
C* are buggy languages and have design faults. A compiler should prevent you from making mistakes and if it allowed you to make mistakes, then this is a bug. I think these ancient languages should never be used in 2023. C was invented in 1972, 51 years ago and C++ in 1985, 38 years ago. Lots of things have changed in the last 20 years. The hardware have improved a lot and many things have been built back better. Yet C* are still clenching to old rules for the sake of backward compatibility, and this is the right thing to do in order not to break the existing code.
Your argument toward Rust is wrong. If - even at that time - you wanted to have a type-safe language that prevented almost all the stuff that Rust claims to fix nowadays you could have used Ada, invented in the 70ies and still active use in areas that are really critical to human mankind. There is no need to write with the new kid on the block. But writing code in Ada is painful.
So i believe that C* should only be used to maintain legacy code, new software should be written in Rust. I see that Rust is the best tool to write C++ like software.
New software should be written in the language you feel most proficient/productive/safe in or which is available for the (embedded) system your are working on., e.g. I had to maintain a legacy project where the only available compiler was JOVIAL. Man I would have killed for a C compiler :D A friend of mine chose Go for the new implementation of industrial router systems as it gave them the best benefit.
Another thing is learning programming for beginners. Learning Rust is very easy and the tooling for Rust is amazing. The official book is not the best and i see it can be written better for people who have never programmed before. But learning C* is a trouble for beginners. And its very easy for beginners to introduce deadly mistakes in C*. You will need a safe, fast, efficient, productive language like Rust to make learning programming easy and fun for as much people as you can. The more new good programmers you make, the more software will get written and the faster a new safer software world will grow.
Use Java as an alternative. The JVM is easy, reliable and you have a working cross-platform UI OOTB. The debugging experience is super nice, the performance is not bad. And you have millions of other coders to ask for help - not forgetting the available frameworks you can use. Students love it…And I think the same applies to .NET languages nowadays.
So maybe yes it's a stupid idea to rewrite Qt in Rust and i no longer support this. But It would be amazing if the developers of Qt contributed in a new Rust GUI framework like Xilem.
Why should a C++ company with many good c++ coders invest in a language they don‘t really speak? And why should a commercial company invest in a (sort of) competing product? Makes no sense to me.
If you want a cross-platform UI for rust, why not have a look at Slint? They claim to be the rust equivalent for Qt written by people who have worked before on Qt.A little more thing to add is that you will always need to build back better from time to time. I think problems in Rust will appear after like 40 years from now and you may need to change things in Rust or build a new language. At this point, a tool can be written to convert the whole existing Rust code to the new language and we may not even need to rewrite anything manually. We should always try to innovate things and not keep sticking to the old stuff.
I really think you are underestimating here the effort it will need to convert code from one language to another. Here is one sample of a tool that converts JOVIAL code to C code. JOVIAL was created roughly 60 years ago and there are people with huge codebases written with it. You can translate it - but is the result really nice? The sample shown in the link is small but I guess it would have been better to have rewritten it from scratch just by looking at the result. It is ugly. The same would apply to rust in the future.
This is my personal point of view.
Mine as well. If some parts may sound rude - this is not intended but I am not a native speaker.
-
@Pete-Carter said in Rewriting Qt in Rust:
My knowledge in C++ is poor BTW.
Hi,
I have a question:
if you don't have rich knowledge about c++
how can you say that c++ is not good and is dying??
I don't know about rust and don't telling rust is bad or dying -
@AliTurk I started my journey in programing in web development using JS and PHP. Then i started learning Rust and some Java. After a while i wanted to take a look at C and C++ to understand Linux, KDE and other code written in C*. After a while of learning C*, i saw that Rust is much better in everything. So i stopped learning more C*.
My knowledge is C* is just the basic stuff, but i don't have to go deep in C* to know that they are much more cumbersome than Rust. If you start using Rust you will know what i mean, especially if you are a beginner. The package manager (cargo) alone is something that makes Rust amazing. Plus all the dependencies in one place (crates.io) and many other stuff. You really don't feel worried writing Rust code unless you activate
unsafe
intentionally.@DerReisende I think that Java and all the other interpreted languages are misused in many places. I believe that any slow resource eating interpreted languages should only be used when absolutely necessary. But developers started using non efficient languages everywhere like in GUIs. The android studio is a very good example for a horrible slow resource eating application that would have been much more efficient if it was written in C++ or Rust instead of Java.
Many programmers just prefer productivity and ease of use of the programming language instead of creating something efficient that will be lightweight on the user's machine. Thanks to Rust we can get the best of both worlds, productivity like python and java, and speed like C++ and C.
-
@Pete-Carter said in Rewriting Qt in Rust:
When i write Rust code and go back to writing C or C++ (C*), i feel like a retard, i feel unsafe and not confident. C* are buggy languages and have design faults. A compiler should prevent you from making mistakes and if it allowed you to make mistakes, then this is a bug. I think these ancient languages should never be used in 2023. C was invented in 1972, 51 years ago and C++ in 1985, 38 years ago. Lots of things have changed in the last 20 years. The hardware have improved a lot and many things have been built back better. Yet C* are still clenching to old rules for the sake of backward compatibility, and this is the right thing to do in order not to break the existing code.
Have to say that if you repeat arguments that people already shown to be false in the same thread, I don't think you are arguing in good faith.
C++ is clearly not your cup of tea, and that's Ok. I don't like Python myself. People have preferences and use what they like to use.
Thanks!
-
@Pete-Carter
ok,
I will try it; it may be a good stuff.
I don't think it would better than c++. -
@Pete-Carter said in Rewriting Qt in Rust:
I think problems in Rust will appear after like 40 years from now and you may need to change things in Rust or build a new language. At this point, a tool can be written to convert the whole existing Rust code to the new language and we may not even need to rewrite anything manually
I don't think that in 40 years time people will be writing (at least procedural) code at all, be it Rust, C* or ANOther! I don't think people will be living on Mars either, nor (at least in a large part of the world) be allowed to smoke or drive a car. Also I don't think England will have won the football World Cup again either... :(
-
@JonB Do not need to wait for that long. Take a look at
https://www.ibm.com/support/pages/ibm-rational-rose-enterprise-7004-ifix001
A great (but expensive) tool. Not a lot of C++ code needs to be written. I tried it before and really loved it. Unluckily, I could not afford it. -
@JoeCFD said in Rewriting Qt in Rust:
@JonB Do not need to wait for that long. Take a look at
https://www.ibm.com/support/pages/ibm-rational-rose-enterprise-7004-ifix001
A great (but expensive) tool. Not a lot of C++ code needs to be written. I tried it before and really loved it. Unluckily, I could not afford it.In 40 years I am going to use ChatGPT-80 to do all the stuff for me :-) After it resurrected me...
-
@JonB said in Rewriting Qt in Rust:
@SimonSchroeder understands Qt signals and thread affinity perfectly well! :) [Well I think so anyway!]
Thank you for jumping to my rescue!
@TomZ said in Rewriting Qt in Rust:
It feels like you might just not have understood signals and QObject thread affinity if you think this is a problem, really.
Maybe, it is a little related to my programming style. We have a huge old source that transitioned between several GUI frameworks. With the last transition from wxWidgets to Qt there was not enough time to rewrite everything (there never is). The easiest migration path was to just run all the work inside a worker thread. Interspersed within the worker thread are lines similar to
ui->lineEdit->setText(str);
. Yes, I could write a new signal for all those lines and then connect slots. But, why should I add two almost useless lines of code? (Signal definition + connect) Instead, we are using QMetaObject::invokeMethod to put the call into the GUI thread. We even have a little wrapper to writeguiThread([this,str](){ ui->lineEdit->setText(str); });
. It would be nice ifui->lineEdit->setText(str);
did all the signal/slot or event loop stuff under the hood. Qt was invented in a time when CPUs had only a single core. The times have changed since then. A good framework should make multi-threading easier for the programmer.@TomZ said in Rewriting Qt in Rust:
If you find any framework of the size we are dealing with that solves it better, please share.
Qt is still the best framework out there. That's why I am using Qt and that's also why I frequent this forum. Otherwise, I'd be gone...
@TomZ said in Rewriting Qt in Rust:
It already is full open source.
If you want to contribute to the official Qt distribution you need to sign an agreement to release your source code both under open source and the commercial version. This is not "normal" open source.
@Pete-Carter said in Rewriting Qt in Rust:
So i stopped learning more C*.
It looks like you have just invented a new term for the hated C/C++. C++ is not meant for writing glorified C code. It is quite easy to stay on the safe side of C++. I've heard that Bjarne Stroustrup is discussing "profiles" as an addition to the standard. These would allow to restrict C++'s functionality. This could be similar to Rust's safe and unsafe mode. Most likely there will be more than 2 profiles if they get implemented.
-
@Pete-Carter said in Rewriting Qt in Rust:
So i believe that C* should only be used to maintain legacy code, new software should be written in Rust. I see that Rust is the best tool to write C++ like software.
Well, your belief is wrong, but more importantly, your argument is also wrong. Such arguments were put forward when Java came to be (popular). Ironically, there were also people who spelled the advent of Java being the demise of the old and generally bad languages (a.k.a. C/C++). It didn't happen, obviously.
-
@SimonSchroeder said:
It would be nice if ui->lineEdit->setText(str); did all the signal/slot or event loop stuff under the hood
Noooo, that would be terrible. I don't want every little ui setter function to have a thread synchronization overhead. Imagine code like this:
ui->lineEdit->setText(str); str = ui->lineEdit->text();
For this to work correctly you would have two blocking thread synchronization points in the setter and getter. That would kill responsiveness of the ui, not to mention things like property animations would grind to a halt. No, thread synchronization should always be explicit, so you can decide if you want to pay for it. It currently is, because signals/slots and meta-invokes are documented to be thread safe, and that's good.
There are multithreaded ui frameworks e.g. Windows API and MFC can have a separate message thread per window, but any needed synchronization between threads is up to the user. -
@Chris-Kawa said in Rewriting Qt in Rust:
Noooo, that would be terrible. I don't want every little ui setter function to have a thread synchronization overhead.
I would not want full synchronization as well. What I am saying is that the trick (for the setter) to queue something in the background (which would be allowed to be asynchronous) would already help a lot. Besides
guiThread(...)
we also haveguiThreadMaybe(...)
which first checks if the calling thread is already the GUI thread. In that case we directly execute the code, otherwise we queue it into the event loop. I am not sure if an extraif
to check for the calling thread would be too slow (I doubt it).You are right about the getter. This is a little bit more complicated. What we do here is to create a mutex in the calling thread (preferably also only if the calling thread is not the GUI thread) to synchronize the call (just this single call) to
str = ui->lineEdit->text()
. The call would again be queued into the GUI event loop. This will only block the calling thread, but not the GUI thread. Again, calling from the GUI thread could (and should) have a direct call for the setter.This is all up on GitHub: https://github.com/SimonSchroeder/QtThreadHelper (though I don't think we included the
guiThreadMaybe(...)
part). I have seen queued calls overtaking each other (updating the progress of the progress dialog). This is why we use an extra queue in this library (by default) to order the calls and only dispatch a single call into the GUI event loop. You see that it is already quite complex. If this were hidden from the user, that would be great.In many cases I also wouldn't care much about synchronization. Right now, if I call
ui->lineEdit->text()
from a non-GUI thread there is a high chance to crash. From my point of view we are just accessing a variable from a different thread. I would expect that I could get an old value, but not that it crashes (doesn'tQString
have copy on write?). Certainly, the actualQLineEdit::setText
should only be executed on the main thread. -
@SimonSchroeder said:
I am not sure if an extra if to check for the calling thread would be too slow (I doubt it)
In case of simple getters like
QString someGetter() { return someMember; }
an extra if on itself would double the cost or more. A branch and threading code inside the getter (even if in the unlikely branch) would very likely prevent the function from being inlined, adding more penalty for rarely used feature.Conceptually I don't think threading is a responsibility of a widget class and that functionality should always be external to it, e.g. with the sorts of helpers you mentioned. Not only is that a good separation of concerns but also mandating such a mechanism would make writing custom widgets a lot harder. Imagine a beginner writing their first custom widget. You really don't want them to be playing with threading while they learn basics.
Right now, if I call ui->lineEdit->text() from a non-GUI thread there is a high chance to crash. From my point of view we are just accessing a variable from a different thread
QString is implicitly shared. It could be detaching or currently edited by the user typing in lineedit. It's a complex class with sharing and dynamic allocation. It's not safe in general to access such structures without ensuring single access in some way, either logically or through explicit synchronisation. QString has COW, but it's not thread safe or even reentrant, nor should it (it's too basic for such overhead).