The Haxe compiler offers opt-in compile-time checking for nullable values. It attempts to catch various possible issues with nullable values.
To enable the checker for a particular class, field, or expression, annotate it with the:nullSafety
metadata. Null safety can be enabled for a whole package using the--macro nullSafety("some.package")
initialization macro.
There are four levels of null safety strictness:
Off
: Turn off null safety checks. Useful to selectively disable null safety for particular fields or expression.Loose
(Default): Within anif (<expr> != null)
condition,<expr>
is considered safe even if it could be modified after the check.Strict
: Full-scale null safety checking for a single-threaded environment.StrictThreaded
: Full-scale null safety checking for a multi-threaded environment.Enabling null safety by default uses the loose strictness level. This can be configured by providing an argument in the metadata:
@:nullSafety(Off)@:nullSafety(Loose)@:nullSafety(Strict)@:nullSafety(StrictThreaded)
Strict
andStrictThreaded
differ in handling of sequential field access.In a multi-threaded application sequential access to the same object field may not yeld the same result. That means a null check for a field does not provide any guarantees:
@:nullSafety(StrictThreaded)functiondemo1(o:{field:Null<String>}) {if (o.field !=null) {// Error: o.field could have been changed to `null`// by another thread after the checktrace(o.field.length); }}@:nullSafety(Strict)functiondemo1(o:{field:Null<String>}) {if (o.field !=null) {trace(o.field.length);// Ok }}
For the package-level case, null safety strictness can be configured using the optional second argument:
--macronullSafety("some.package",Off)--macronullSafety("some.package",Loose)--macronullSafety("some.package",Strict)--macronullSafety("some.package",StrictThreaded)
Null<T>
(assignments, return statements, array access, etc.).@:nullSafetyclassMain {staticfunctiongetNullableStr():Null<String> {returnnull; }publicstaticfunctionmain() {functionfn(s:String) {}varnullable:Null<String> =getNullableStr();// all of the following lines would cause a compilation error:// var str:String = null;// var str:String = nullable;// fn(nullable); }}
==
and!=
) is not allowed.Null<>
then it should have an initial value or it should be initialized in the constructor (for instance fields).varnullables:Array<Null<String>> = ['hello',null,'world'];// Array<Null<String>> cannot be assigned to Array<String>://var a:Array<String> = nullables;
null
are considered safe inside of a scope covered with that null-check:varnullable:Null<String> =getSomeStr();//var s:String = nullable; // Compilation errorif (nullable !=null) {s =nullable;//OK}//s = nullable; // Compilation errors = (nullable ==null ?'hello' :nullable);// OKswitch (nullable) {casenull:case_:s =nullable;// OK}
functiondoStuff(a:Null<String>) {if(a ==null) {return; }// From here `a` is safe, because function execution// will continue only if `a` is not null:vars:String =a;// OK}
null
, but Haxe types them withoutNull<>
.vara:Array<String> = ["hello"];$type(a[100]);// Stringtrace(a[100]);// nullvars:String =a[100];// Safety does not complain here, because `a[100]` is not `Null<String>`, but just `String`
null
values. Null safety cannot protect against this.vara:Array<String> = ["hello"];a[2] ="world";trace(a);// ["hello", null, "world"]vars:String =a[1];// Cannot check thistrace(s);//null
null
values will come into your code from third-party code or even from the standard library.null
. You can use helper methods instead:usingMain.NullTools;classNullTools {publicstaticfunctionsure<T>(value:Null<T>):T {if (value ==null) {throw"null pointer in .sure() call"; }return @:nullSafety(Off) (value:T); }publicstaticfunctionor<T>(value:Null<T>,defaultValue:T):T {if (value ==null) {returndefaultValue; }return @:nullSafety(Off) (value:T); }}classMain {staticvarnullable:Null<String>;publicstaticfunctionmain() {varstr:String;if (nullable !=null) {str =nullable;// Compilation error }str =nullable.sure();str =nullable.or('hello'); }}
vara:Null<String> =getSomeStr();varfn =function () {if (a !=null) {vars:String =a;// Compilation error }}
Unless the closure is executed immediately:
vara:Null<String> =getSomeStr();[1,2,3].map(function (i) {if (a !=null) {returni *a.length;// OK }else {returni; }});
varnullable:Null<String> =getSomeNullableStr();varstr:String;if (nullable !=null) {str =nullable;// OKdoStuff(function ()nullable =getSomeNullableStr());if (nullable !=null) {str =nullable;// Compilation error }}