Godot: Erasing an element from a dictionary while iterating fails silently

Created on 13 May 2018  路  9Comments  路  Source: godotengine/godot

Godot 3.0.2
Windows 10 64 bits

I've been debugging a strange behavior in my editor plugin for an hour, until I realize it was due to my tool script:
I was removing an element from a dictionary, while iterating on that same dictionary. It was hard to notice because the place where I was iterating is far from the place where I erase.
As a result, the iteration abruptly skipped all the following keys, but SILENTLY.
I was using the commandline debugger to catch as much errors as possible, but that one went through unnoticed.

Repro:

var d = {"a": 1, "b": 2, "c": 3}
for k in d:
    d.erase(k)
print(d.keys())

Prints:

[b, c]

If Godot doesn't support this without copying all the keys, I expected an error.

bug confirmed gdscript

Most helpful comment

Still valid in 07db4d57d

As someone commented, doing for k in d.keys() in the repro code will produce a correct result. So iterating normally should throw an error/warning when the keys are modified and point to use keys().

All 9 comments

Yeah, the pythons are doing it right:
RuntimeError: dictionary changed size during iteration

Someone could at least put a loud DON'T DO THIS note in the docs ;)
http://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/gdscript_basics.html#dictionary
http://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/gdscript_basics.html#for

I wouldn't expect this to work anyway, but it should throw some error. However, I'm not sure if this can be properly detected.

I wouldn't expect this to work anyway

why not? looks valid to me.

the bug should be, why this isn't working.

why not? looks valid to me.

Because if you destroy what you have under your feet, then you can't jump?

Because if you destroy what you have under your feet, then you can't jum

but it's just a key (a part of the dictionary), not "what you have under your feet"

For the sake of efficiency in implementation, a dictionary can not be changed while iterating over it. A serious language designer wouldn't allow that.

Iterating over the key array it works fine. Try it:

var d = {
    "Alle meine": 1,
    "Entchen": 2,
    "schwimmen auf dem": 3,
    "See": 4,
}
print("before ", d)
for k in d.keys():
    d.erase(k)
print("after ", d)

https://github.com/godotengine/godot/blob/master/core/ordered_hash_map.h (which dictionaries seem to be based on) states:

Deletion during iteration is safe and will preserve the order.

According to reduz, this should probably still be true - so something fishy seems to be going on here.

According to reduz, this should probably still be true - so something fishy seems to be going on here.

This is probably true from the C++ code, where you can iterate as a list of pairs. I don't think GDScript is iterating that way.

Still valid in 07db4d57d

As someone commented, doing for k in d.keys() in the repro code will produce a correct result. So iterating normally should throw an error/warning when the keys are modified and point to use keys().

Was this page helpful?
0 / 5 - 0 ratings