Non-deterministic order of signal propagation
Race conditions in single-threaded QML/QtQuick? It's more likely than you think!
Properties in QML are known to be declarative, and are so convenient that you may be tempting to use them for everything. But let's break down a case where things might go subtly south.
Situation
Suppose you have a source property abc which you have to validate before using it. It may be defined in QML or C++ — it doesn't matter; so let's use QML for simplicity.
property int abc
The validation code could be an arbitrary complex condition, or could even be as simple as reading only the property value itself:
// OK
if (abc && somethingElse) {
use(abc);
}
// Also OK
if (abc) {
use(abc);
}
The validation expression becomes long and repetitive, so at some point you decide to factor it out into a property, and replace it in consumers:
// Bad
readonly property bool check: abc && somethingElse
// or simply
readonly property bool check: abc
// some other property binding or signal handler
consumer: {
if (check) {
use(abc);
}
}
It doesn't matter how complex your check expression is, it would be equally broken at this point. But not always. Sometimes. Maybe? It's non-deterministic, after all!
Welcome to street racing
So what could possibly go wrong with that check intermediary property as a condition?
First, we need to understand how the QML engine does it magic, how it knows when to automatically re-evaluate property bindings.
In Qt all properties have some sort of READ function. In C++ READ may simply return a value from a backing storage (there's also a MEMBER syntax for such cases which reduces the boilerplate), or it can be an arbitrary complex method with all sorts of custom logic in it. But properties declared in pure QML (as of Qt 6.8) are all stored, and there's no support for JavaScript get/set yet.
Thus, reading a property declared in pure QML is guaranteed to return its stored value. It will NOT cause the property's binding to re-evaluate.
Second, in Qt properties should either be CONSTANT or have an associated NOTIFY signal — and if they don't, MOC complains at compile time. We are interested in the ones with NOFITY here. Luckily, the QML engine generates associated NOFITY signals for properties declared in pure QML for us.
During a property binding evaluation, whenever the engine READs a property which happens to have a NOTIFY, the engine would connect that signal to a handler that would re-evaluate the property:
consumer: abc ? use(abc) : null
// connect(abcChanged, updateConsumer)