[Development] Redesigning QML value types

Ulf Hermann ulf.hermann at qt.io
Tue Sep 20 18:13:03 CEST 2022


Hi,

I'm currently trying to transform QML value types, such as font, rect, 
size, etc, into something less random and more predictable. On that 
occasion I'm wondering what semantics people actually expect from value 
types.

One thing you have to know in advance is that QML has this internal 
concept of "value type references". A value type reference is object of 
a value type combined with a sort of pointer to the property it was 
retrieved from. Whenever the value type object is modified, it is 
subsequently written back to its original property. This mechanism is 
mildly inefficient, as you may imagine. Yet, we use it all over the place.

Now, consider the following snippet of QML:

import QtQuick
QtObject {
     property font f
     function doEvil(ff: font) { ff.pointSize = 22 }
     Component.onCompleted: doEvil(f)
}

Assume the default pointSize of a font object is 0. What pointSize 
should the member 'f' of the created object receive here?

I see two ways of looking at this:



1. Value types are passed by value
----------------------------------

The doEvil() function then receives a copy of the font and the original 
font is never modified. Its pointSize stays 0. If this is the way it 
should behave, you get to tell me what the following should do, though:

Component.onCompleted: {
     var ff = f;
     ff.pointSize = 22;
}

In JavaScript, this is indistinguishable from simply "f.pointSize = 22". 
So, strictly speaking, the latter would have to modify a copy of 'f', 
not 'f' itself. That, however, would make working with value types 
somewhat more complicated. You'd have to explicitly write them back 
after modification. On the plus side, you could modify several 
properties in a row and only write the value back once.

Since value types have been writing themselves back for about 10 years 
now, we'd need some compatibility mechanism to phase such a behavior in, 
over several versions of Qt. And I'm sure someone would hate it.

However, since we've never implemented value type references for lists, 
lists of value types behave exactly this way. The following does in fact 
not modify the list, but only a copy of the object at index 10:

property list<font> fonts: [ ... ]
Component.onCompleted: fonts[10].pointSize = 33

The same holds for nested value types. Assume a value type "outer" that 
has a property "a" of another value type, which has an integer property 
"b". The following does not write back:

property outer o
Component.onCompleted: o.a.b = 10



2. Everything is a reference
----------------------------

JavaScript doesn't really have value types. The doEvil() function thus 
receives a reference to the original 'f', not a copy. The modification 
of pointSize is written back to the original property.

With font you might still wrap your head around it because font "looks 
like" an object with all its properties and internal logic. However, you 
can also pass a point or a rect around through various functions, and 
whenever you modify it, the result would be written back to the original 
place where it was retrieved from, possibly in a different file half an 
hour ago. I personally find that rather confusing.

Another drawback of this is that it would be rather hard to compile a 
function that deals with value types to C++. In C++, value types don't 
have a back reference to the property they were loaded from. A QML font 
is just a QFont and nothing else. This assumption allows qmlcachegen and 
qmlsc to generate efficient code. Yet, as you may notice by toggling 
QV4_FORCE_INTERPRETER on the QML program above, they already mess this 
up today (and no one has noticed).

So, the consequence of going with "Everything is a reference" would be 
that we couldn't compile any function to C++ that passes a value type to 
a JavaScript function or uses a value type returned from a JavaScript 
function.



A middle ground would be that value types are passed by value when 
calling or returning from a function, but are treated as references and 
written back on modification inside a function. Inside the same function 
qmlsc and qmlcachegen could keep track of where a value was loaded from 
and write it back when it's modified. I've played with the concept a 
bit, and it's possible to support nested value types and lists of value 
types this way. All the writing back would still be inefficient, though.

In addition to whatever course is chosen, we might add a detach() method 
on the JavaScript prototype used for all value types. With that you 
could intentionally detach a value from the property it was loaded from.


regards,
Ulf


More information about the Development mailing list