This library implements a few features for writing Haxe code which generates less null pointer errors. These features are: static extensions for more enjoyable null safety, safe navigation operator, safe arrays, safe api (automatic checks of method arguments for null).
Haxe 4 is required.
Install Safety from haxelib:
haxelib install safety
or the latest development version from github:
haxelib git safety https://github.com/RealyUniqueName/Safety.git
Add -lib safety to your hxml file.
Use following compiler arguments:
--macro Safety.safeNavigation(dotPath, recursive)- Enables safe navigation operator!.in the specified path. Ifrecursiveistrue(it is by default), then!.operator is also enabled for subpackages indotPath.--macro Safety.safeArray(dotPath, recursive)- Makes all array declarations to be typed asSafeArray. See feature description for details.--macro Safety.safeApi(dotPath, recursive)- Adds runtime checking for not-nullable arguments of public methods in the specified path. Ifnullis passed to such an argument, thensafety.IllegalArgumentExceptionis thrown. Details
All --macro Safety.* arguments can be used multiple times with different dotPath values.
You can pass empty string "" as any dotPath to apply a feature to the whole codebase (not recommended, because a lot of compilation errors will come from std lib).
Usage example:
using Safety;
var nullable:Null<String> = getSomeStr();
var s:String = nullable.or('hello');Available extensions:
/**
* Returns `value` if it is not `null`. Otherwise returns `defaultValue`.
*/
static public inline function or<T>(value:Null<T>, defaultValue:T):T;
/**
* Returns `value` if it is not `null`. calls `getter` and returns the result.
*/
static public inline function orGet<T>(value:Null<T>, getter:Void->T):T;
/**
* Returns `value` if it is not `null`. Otherwise throws an exception.
* @throws safety.NullPointerException if `value` is `null`.
*/
static public inline function sure<T>(value:Null<T>):T;
/**
* Just returns `value` without any checks, but typed as not-nullable. Use at your own risk.
*/
static public inline function unsafe<T>(value:Null<T>):T;
/**
* Returns `true` if value is not null and `callback(value)` is evaluated to `true`.
* Returns `false` otherwise.
*/
static public inline function check<T>(value:Null<T>, callback:T->Bool):Bool
/**
* Applies `callback` to `value` and returns the result if `value` is not `null`.
* Returns `null` otherwise.
*/
static public inline function let<T,V>(value:Null<T>, callback:T->V):Null<V>;
/**
* Passes `value` to `callback` if `value` is not null.
*/
static public inline function run<T>(value:Null<T>, callback:T->Void):Void;
/**
* Applies `callback` to `value` if `value` is not `null`.
* Returns `value`.
*/
static public inline function apply<T>(value:Null<T>, callback:T->Void):Null<T>;Adds safe navigation operator !. to Haxe syntax. Compiler argument to enable it: --macro Safety.safeNavigation(my.pack)
var obj:Null<{ field:Null<String> }> = null;
trace(obj!.field!.length); //null
obj = { field:'hello' };
trace(obj!.field!.length); //5SafeArray<T> (abstract over Array<T>) behaves exactly like Array<T> except it prevents out-of-bounds reading/writing (throws safety.OutOfBoundsException). Writing at an index of array's length is allowed.
See Null safety limitations to find out why you need it.
If --macro Safety.safeArray(my.pack) is in effect, then all array declarations become SafeArray:
var a = ['hello', 'world'];
$type(a); //SafeArray<String>You can use stdArray() method to get a reference to the Array<T>:
var a = ['hello', 'world'];
$type(a.stdArray()); //Array<String>Compiler argument to enable this feature: --macro Safety.safeApi(my.pack).
If enabled it adds runtime checking against null for all not-nullable arguments of public methods:
public function method(arg:String) {}
<...>
method(null); //throws safety.IllegalArgumentExceptionIt's pretty cheap performance wise, because it just adds a simple line to the body of a method: if(arg == null) throw new IllegalArgumentException();
If argument is nullable, no check is generated.
Also safe api does not generate such checks for Int, Float, Bool (and other basic types) on static targets, because it's impossible to assign null to such types on static targets.
This feature is especially useful if you are creating a library, and you don't want your users to pass nulls to your API. You can add --macro Safety.safeApi('my.lib') to extraParams.hxml.
- Out-of-bounds array read returns
null, but Haxe types it withoutNull<>. (PR to the compiler to fix this issue)
var a:Array<String> = ["hello"];
$type(a[100]); // String
trace(a[100]); // null
var s:String = a[100]; // null-safety does not complain here, because `a[100]` is not `Null<String>`, but just `String`- Out-of-bounds array write fills all positions between the last defined index and the newly written one with
null. null-safety cannot save you in this case.
var a:Array<String> = ["hello"];
a[2] = "world";
trace(a); //["hello", null, "world"]
var s:String = a[1]; //null-safety cannot check this
trace(s); //null- Haxe was not designed with null safety in mind, so it's always possible
nullwill come to your code from 3rd-party code or even from std lib. - Nullable fields and properties are not considered null-safe even after checking against
null. Use safety extensions instead:
using Safety;
class Main {
var nullable:Null<String>;
function new() {
var str:String;
if(nullable != null) {
str = nullable; //Compilation error.
}
str = nullable.sure();
str = nullable.or('hello');
}
}