Description
Tested versions
v4.4.stable.official [4c311cb]
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:
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: <null>
Expected Behavior:
am_i_one
should correctly be initialized to1
.
Actual Behavior:
am_i_one
is<null>
becauseMyClass.i_will_be_one
is not initialized befoream_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:
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 onB.j
, andB.j
depends onA.i
, causing a circular dependency.- Java detects this and assigns both values their default (
0
forint
). - However, if either
A.i
orB.j
were explicitly set to1
, Java would propagate that value correctly instead of defaulting to0
, demonstrating its ability to intelligently resolve dependencies when possible.
Java Example with Explicit Initialization:
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 to1
, Java correctly resolvesB.j = A.i
to1
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:
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 resolvesA.i = B.j
to1
. - 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:
- If one static field directly depends on another, Java ensures that the dependent class is loaded first.
- If circular dependencies exist, Java assigns default values instead of leaving them uninitialized.
- If at least one of the dependent variables is explicitly initialized, Java correctly resolves and propagates that value.
- 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:
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:
- Ensure that static variables are initialized in a deterministic order, where parent class static variables are initialized after inner class static variables.
- Introduce a lazy initialization mechanism to defer value retrieval until all related static variables are set.
- 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
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: <null>
Minimal reproduction project (MRP)
N/A
Metadata
Metadata
Assignees
Type
Projects
Status
For team assessment