godotengine / godotengine/godot
Static Variable Initialization Order Issue in GDScript
- Dominant language
- C++
- Stars
- 117k
- Forks
- 26.8k
- PR merge metrics
- PR metrics pending
Description
### Tested versions
v4.4.stable.official [4c311cbee]
### System information
Godot v4.4.stable
### Issue description
**Description:**
When defining a static variable that depends on another static variable from a nested class, GDScript fails to initialize it in the expected order, resulting in `null` instead of the intended value.
**Steps to Reproduce:**
```gdscript
extends Node
class MyClass :
static var i_will_be_one = 1
static var am_i_one = MyClass.i_will_be_one
func _ready():
print("am_i_one: ", am_i_one) # Expected: 1, Actual:
```
**Expected Behavior:**
- `am_i_one` should correctly be initialized to `1`.
**Actual Behavior:**
- `am_i_one` is `` because `MyClass.i_will_be_one` is not initialized before `am_i_one` is assigned.
**Analysis:**
This issue seems to be related to **static variable initialization order**. In some other languages like Java, similar issues arise when circular dependencies exist between static initializations. Java solves this by ensuring that static fields are initialized in a intelligent order.
### Java Example of a Similar Issue:
```java
class A {
static int i = B.j; // Depends on B.j
}
class B {
static int j = A.i; // Depends on A.i
}
public class Main {
public static void main(String[] args) {
System.out.println("A.i: " + A.i); // 0
System.out.println("B.j: " + B.j); // 0
}
}
```
**Explanation:**
- `A.i` depends on `B.j`, and `B.j` depends on `A.i`, causing a circular dependency.
- Java detects this and assigns both values their default (`0` for `int`).
- However, if either `A.i` or `B.j` were explicitly set to `1`, **Java would propagate that value correctly instead of defaulting to `0`**, demonstrating its ability to intelligently resolve dependencies when possible.
### Java Example with Explicit Initialization:
```java
class A {
static int i = 1; // Explicitly set to 1
}
class B {
static int j = A.i; // Depends on A.i
}
public class Main {
public static void main(String[] args) {
System.out.println("A.i: " + A.i); // 1
System.out.println("B.j: " + B.j); // 1
}
}
```
**Explanation:**
- Since `A.i` is explicitly initialized to `1`, Java correctly resolves `B.j = A.i` to `1` as well.
- This shows that Java does not simply assign default values arbitrarily but instead attempts to propagate known values when possible.
### Another Java Example with Reversed Dependency:
```java
class A {
static int i = B.j; // Depends on B.j
}
class B {
static int j = 1; // Explicitly set to 1
}
public class Main {
public static void main(String[] args) {
System.out.println("A.i: " + A.i); // 1
System.out.println("B.j: " + B.j); // 1
}
}
```
**Explanation:**
- Since `B.j` is explicitly initialized first, Java correctly resolves `A.i = B.j` to `1`.
- This further demonstrates Java’s ability to intelligently propagate initialization when possible.
### Java's Solution:
Java prevents this issue by enforcing a well-defined initialization order:
1. If one static field directly depends on another, Java ensures that the dependent class is loaded first.
2. If circular dependencies exist, Java assigns default values instead of leaving them uninitialized.
3. If at least one of the dependent variables is explicitly initialized, Java correctly resolves and propagates that value.
4. Developers can avoid potential order issues by restructuring dependencies or using **lazy initialization**.
### GDScript's Limitation:
Unlike Java, GDScript lacks static initialization blocks and mechanisms for deterministic static initialization ordering. Additionally, other issues like:
```gdscript
static var am_i_one_too = am_i_one # null
static var am_i_one = 1
```
show that even within a single class, the order of static variable initialization can cause unexpected `null` values, making it difficult to rely on static variables safely.
**Possible Solutions:**
1. Ensure that static variables are initialized in a deterministic order, where parent class static variables are initialized after inner class static variables.
2. Introduce a lazy initialization mechanism to defer value retrieval until all related static variables are set.
3. Provide a warning or error message when an uninitialized static variable is accessed during initialization.
**Impact:**
This issue can lead to unexpected behavior in complex scripts where static variables are used for configuration or constants shared across classes.
**Workaround:**
Manually assign the value in `_ready()` instead of relying on static initialization.
### Steps to reproduce
```gdscript
extends Node
class MyClass :
static var i_will_be_one = 1
static var am_i_one = MyClass.i_will_be_one
func _ready():
print("am_i_one: ", am_i_one) # Expected: 1, Actual:
```
### Minimal reproduction project (MRP)
N/A
Contributor guide
Research direction
Start by running the GDScript reproduction in the issue on Godot v4.4.stable and confirm that am_i_one prints null instead of 1. Trace static variable initialization for the nested class and the containing script; done means the dependency initializes deterministically or the behavior is clearly diagnosed with a regression test.
Written by the indexing model from the issue text.
Assessment
- Domain
- game-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100