Make null
vs Freed Object
behavior more consistent
#10098
dalexeev
started this conversation in
Engine Core
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Origins
"The most important data type in Godot" is
Variant
. All values in GDScript areVariant
. It has two fields: type (TYPE_NIL
,TYPE_OBJECT
, etc.) and content (a union). Also Godot/GDScript does not have a garbage collector. This means that objects can be destroyed before all references to the object become unreachable. Therefore, we have several possibilities to have an "invalid object" (null
-like values):null
(type isTYPE_NIL
, content is zero)Object#null
(type isTYPE_OBJECT
, content is zero)Object#null
(Ref<T>()
) instead ofnull
Variant()
. For example,Array.get_typed_script()
.Freed Object
(type isTYPE_OBJECT
, content is non-zero, after the object is destroyed)ObjectID
inOBjectDB
. TheVariant
data does not change when the pointed object is removed from memory.Problems
While working with the engine, you come across various examples of value comparisons and type conversions (both explicit and implicit):
str()
andvar_to_str()
. Usuallyvar_to_str()
gives you more details, but in the case ofnull
-like values this is not true, they are all converted tonull
becausevar_to_str()
is designed for text serialization.<null>
in the debugger godot#92632.bool()
constructor, but in 4.x it supports few types.is_empty()
,is_valid()
,is_null()
. That last one is confusing.if
,elif
), loops (while
) and logical operators (and
,or
,not
), values are cast tobool
implicitly.==
operator allows you to compare values in a "reasonable" way. In some cases, similar values of different types (such as1
and1.0
) are considered equal, in other cases this results in an error.===
for comparing references and variants (implemented asis_same()
method) #6092.is_same()
for strict comparisons.match
statement has special behavior that differs from both operator==
andis_same()
.in
keyword.Array.has()
,Dictionary.has()
,Dictionary.find_key()
and others.is
keyword, global functionstypeof()
andis_instance_of()
.as
keyword, constructors, special methods, global functionsconvert()
andtype_convert()
. There is also implicit type casting in a typed context (assignment, argument passing, etc.).Freed Object
, but not fornull
.Value comparisons and type conversions in 3.x and 4.x
3.5.3
4.2.2 -> 4.3 dev 6
Summary
3.x -> 4.0
For some reason, maybe by mistake, the following changes occurred during the 4.x implementation:
if not freed
now evaluates to true, just likeif freed
. Obviously it's a bug, if one is true, then the other should be false.Freed Object
now equalsnull
andObject#null
.Proposal Options
Option 1. Restoring 3.x Behavior
This makes sense, but is less beginner friendly and breaks compatibility with 4.0-4.2. In addition, there are other changes compared to 3.x. Finally, 4.x has the global function
is_same()
. So the of 3.x behavior in itself cannot justify this option.Option 2. Intuitive and Beginner Friendly
This option breaks compatibility to a lesser extent, it only fixes already broken behavior. This also makes the use of the more verbose
is_instance_valid()
optional.Option 3. godotengine/godot#73896 (reverted)
Note that godotengine/godot#73896 mixes the two above options, which seems like a less consistent move.
Comparison Styles
I compared existing options for comparing
null
-like values used in various places in the engine. We should take this into account when making a decision.1. Intuitive
Treat
null
,Object#null
andFreed Object
as equal. This is the most intuitively expected behavior for users coming from languages with garbage collection.==
fornull
-like values in 4.0-4.2, 4.3 dev 1-5, and current master2. Strict
Treat values as different if they differ by
Variant.Type
and/or content (for reference types, the content is the reference).Note that for dictionary keys this is the only option, since the keys must be unique. If you add two object keys to a dictionary and then free the objects, you should not end up with two equal (in
Dictionary
terms) keys in one dictionary.1is_same()
Dictionary.has()
==
for dictionaries (keys)match
for dictionaries (keys)3. By
Variant.Type
Treat
null
andObject#null
/Freed Object
as different (because they have different types:TYPE_NIL
andTYPE_OBJECT
), butObject#null
andFreed Object
as equal (because both areTYPE_OBJECT
and point to an invalid instance).Array.has()
==
for arraysDictionary.find_key()
(find key by value)==
for dictionaries (values)match
fornull
-like valuesmatch
for arraysmatch
for dictionaries (values)4. By
ObjectID
Treat
null
andObject#null
as equal (because they have a zeroObjectID
), butnull
/Object#null
andFreed Object
as different (becauseFreed Object
has a non-zeroObjectID
). TwoFreed Object
s are equal if they have equalObjectID
s.==
fornull
-like values in 3.x, 4.3 dev 6 - beta 2Footnotes
Note that the
Dictionary
treatsString
andStringName
as the same. But it's safe because, unlike objects, two "equal"String
/StringName
can never become different. ↩Beta Was this translation helpful? Give feedback.
All reactions