TLDR; in Argentum there is no separate nullable, bool and optional types. It's the same concept, thus all operations, guaranties and safety measures are uniformly and seamlessly applicable all of them.
Nowadays, it has become fashionable to add Null safety to all programming languages. In Argentum, Null safety is achieved not by adding a new machinery, but by removing unnecessary one.
Argentum pointers are never nullable. If a nullable pointer is needed, an optional wrapper around the pointer is used, and optional-nothing does the same role as null-pointer in other languages:
// `a` is a non-nullable reference, initialized
// with a freshly constructed instance of `Point`
a = Point;
// `b` is an optional/nullable reference to `Point`,
// initialized with a `nothing` value.
b = ?Point;
b := a; // Now `b` reference the same location as `a`
b := Point; // Now `b` reference its own freshly created instance of `Point`
b := ?Point; // Now `b` is optional-nothing again.
v = b.x; // Type error: `b` is not a pointer
// Ok, `b` is an `optional`, and as such a `bool` generalization.
// So no weird `??` `or_else` `?.` or other fancy grammar, just old good
// well-known former bool operations now generalized for all optionals..
v = b ? _.x : 0;
By the way, the syntax ?T
for creating empty pointers signifies that in Argentum, all "null pointers" are strictly typed with their normal types and types are not erased when converted to and from "nulls".
In Argentum, the optional type is deeply integrated into the language. For different wrapped types, its internal representation varies. For example, optional pointers are actually stored as regular pointers, and optional-nothing
is encoded as 0
. This provides effortless marshaling to other languages through FFI, compactness of internal representation, and high operational speed.
From the language perspective, types Object
and ?Object
only differ at the compilation stage - the former doesn't require null-checks, while the latter, on the contrary, forbids access without checking. A similar approach is used for ?double
, where optional nothing is simply NaN
.
Since null pointers are mere optionals, and Argentum disallows to access inner value of any optional without prior checking for nothing-ness, Argentum programs cannot dereference null-pointers without null-checks. It's a syntax-driven safety.
On the other hand, once checked, value unwraps from optional and requires no more checking:
fn doSomething(maybeObject ?MyClass) { // function explicitly declared as taking nullable
maybeObject ? { // A single check
_.method(); // From now on, no checking is needed
log(_.field);
_.field := expression;
doSomethingDifferent(_); // Call function, expecting non-nullable
}
}
fn doSomethingDifferent(obj MyClass) { // Non-nullable pointer
obj.method(); // No checking needed
doSomething(obj); // Auto-conversion T -> ?T
}
In the last line when a MyClass
value passed where a ?MyClass
is expected, it wraps in optional automatically. Sometimes it's desirable to do this conversion explicitly:
// Variable `a` of type `?int` holding value 42
a = +42;
// Variable `c` of type `?Point` holding a newly created `Point` instance
c = +Point
Some might say Rust (Dart, Swift, Go) also has null-safety. Yes, but:
- Using weird non-standard language constructs
- Allowing to bypass null-safety or crash on nulls when you believe nothing happens
And since optional is a superset of bool, null checks are performed by the standard bool-related operators. Welcome back C/C++ style: if(ptr) use(ptr);
this time in a safe and strictly typed manner: ptr ? use(_)