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.
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.
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");
}
}
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>
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.
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.