Fail on uninitilized access

Is it possible to have the compiler fail on uninitialized access inside the constructor of a class…
for example currently if I have a class member that is an anonymous structure it will have a runtime error every time for this class, which I would like to catch at compile time.

class Test {
    static function main() {
        var badinstance = new ErrorClass();
        trace(badinstance);
    }
}

class ErrorClass
{
    public function new()
    {
        //  this access tries to write to an member of uninitialized anonstruct
        // however to initialize anonstruct I would have to declare the full json 
        // description including member 1 and member 2 (just to set member1)
        // should anonstruct be default initialized (to an empty object) or is it just considered like any other object pointer?
        // if it is simply considered a normal object pointer should this constructor fail to compile
        // since it is a guaranteed crash scenario
        // id like the compiler to fail to compile with a 'attempt to use "anonstruct" prior to initialization' message
        anonstruct.member1 = true;
    }
    public var anonstruct:{
        member1:Bool,
        member2:Bool
    }
}

Anon structures are normal objects, so no default initialization. In fact Haxe does not do any default initialization at all. You might want to look into new Haxe 4 features: final fields that require initialization and @:nullSafety that checks that non-nullable fields are initialized.

The ‘final’ keyword is actually somewhat along the lines of what I’m looking for thanks for that.

Am I correct in understanding @nullSafety is a way to prevent null from being written to a variable,member, or parameter?

Is anyone working on compile time checks to prevent uninitialized reads? Is there a valid use case where someone would want to perform an uninitialized read in a constructor?

For example:


class Test
{
	public function new()
	{
		trace(a); // this line is never really valid code and shouldn't compile
		a = false;
		if (Std.random(2) == 1)
			b = true;
		trace(b); // should also fail since b does not always have an assigned value
		if (b == null) // or perhaps it should compile because folks could do a null test on the variable later
			trace("b was set");
		// whoops not all control paths set the value for b (this still compiles in 4.0.0-rc.2)
	}

	public final a:Bool;
	public final b:Bool;
}

Also the only reason I got caught on this was my lack of understanding of the language. Something like this would probably help seasoned pros less than folks new to Haxe.

Null safety also checks that class fields are initialized before allowing you to read them.

Wow, works as intended and does not appear to cause any change in the generated code. That’s a really neat feature you’ve made. I totally didn’t see any section of the github page mentioning uninitialized reads as one of its features. Also didn’t find the presentation you made originally(quite well thought out), explains the feature quite well. Id be really tempted to make this the default behavior of haxe in my future builds.

https://haxe.org/videos/conferences/haxeup-sessions-2018/null-safety-alexandr-kuzmenko.html

How would I go about accessing a static Null member that I’m sure is not null… I’m compiling with --macro nullSafety("",Strict,true) and it doesn’t want to allow me to call a method on member. The compile error is “Null safety: Cannot access “hello” of a nullable value.” Thanks for all your assistance.

class Tests
{
	static function main():Void
	{
		member = new CantBeNull();
		member.hello();
	}

	static var member:Null<CantBeNull>;
}

class CantBeNull
{
	public function new() {}

	public function hello():Void
	{
		trace("Hello");
	}
}

In strict mode that is not allowed because member could have been changed to null by that time. E.g. from another thread.

You may do this:

switch member {
	case null:
	case m:	m.hello();
}

or if you use something like Safety library:

using Safety; //better put this into import.hx

member.sure().hello();

Or you can switch to loose null safety to avoid losing your sanity. :smiley:

I’m not sure why you guys are talking about Strict mode here. There’s no null check in the example code so this won’t work in any mode. If something can’t be null, it should just not be typed as Null<T> :slight_smile:

There are cases where we can’t initialize the non-nullable field right away, but if we know for sure it’s going to be initialized before any usage, and this is the case where we should use @:nullSafety(Off) metadata on the field itself, to disable the initialization check.

There’s no null check in the example code so this won’t work in any mode.

Actually, it works in Loose mode because it is set to non-nullable value prior to reading from it.

Ahh, you’re right. Still the poing about non-nullable values should not be Null<T> stands :slight_smile:

1 Like

My example is a pretty contrived one. The actual scenario I’m working with is for a singleton class that does start out with a null value for its instance.

which haxe feature turns the Null into a T inside the switch?

switch member {
	case null:
	case m:	m.hello();
}

this has the same structural meaning but fails to compile as you say it could be changed in another thread:

if(member!=null)
   member.hello();

I will probably end up using the safety library, I mainly want to try and understand how to use the feature in the core language. I see in the safety library you turn off null safety in order to perform the typecast, would that also not carry the risk of the value changing in another thread?

this seems to compile…

	static public inline function sure<T>(value:Null<T>):T
	{
		switch (value)
		{
			case null:
				throw "value cant be null";
			case a:
				return (a : T);
		}
	}

The switch return actually generates an extra assignment in the compiled code so its gotta be slightly less performant. Calling the sure method makes a copy into value making anything you do to it safe, therefore not requiring using the switch inside the safe method.