Friday, May 12, 2017

How to create an operator overload that only accepts nil

Since the introduction of Nullable types in Spring4D they had a problem: assigning null to them was cumbersome. Some time ago you could implicitly assign a Variant to them which made it possible to assign Null. However that caused some issue because of implicit Variant type conversion.

Imagine you had a Nullable<Integer> which should only allow Null or some Integer. But in fact you could assign a string to it - not good. Why was that the case? Well because it had an implicit operator for Variant to Nullable<Integer> and the compiler was too smart converting the string into a Variant to pass that to the operator overload. Result was an exception at runtime - very bad considering that Nullable<T> should be type safe at compile time.

Next idea was making an overload for Pointer to Nullable<T> but that made it possible to pass all kinds of reference types to a nullable - another source for bugs.

Before anyone asks, a Clear method was also never an option because the type should be immutable. Think of a property. If you would call Clear on that you would just clear the value returned from the getter.

For 1.2 I used an explicit type with no state and a static property Null so you can assign or compare with Nullable.Null (suggestion by Sir Rufo). An option but it always bugged me because it was not as crisp as just assigning nil like in Swift or null in C#.

Today it struck me. Use some reference type that you cannot assign anything else but nil to. How is that possible? Make it private so you cannot declare any variable of that type.

My first attempt was this:

type
  Nullable<T> = record
  strict private type
    Null = class end;
  public
    class operator Implicit(const value: Null): Nullable<T>;
  end;

That kinda worked. It is impossible to declare a variable of Null and thus you cannot assign anything but nil to it. But wait. You can assign any untyped pointer to it (and declaring $TYPEDADDRESS in the unit the Nullable<T> type is declared in did not work). Well, are there any types that don't allow assigning some untyped pointer to? Yes, events or managed types like dynamic arrays or interfaces. I tried them all and if I did not miss anything it works with a private interface type - I was not able to assign any typed or untyped pointer, interface or object to it.

So this is what worked (it looks a bit different in Spring.pas because I did not put the type into the generic Nullable<T> type but somewhere else where it is not accessible except from the same unit)

type
  Nullable<T> = record
  strict private type
    Null = interface end;
  public
    class operator Implicit(const value: Null): Nullable<T>;
  end;

Now (starting with Spring4D 1.2.1) you can finally simply assign nil to a Nullable<T> without losing type safety.

P.S. By default the Nullable<T> is still in compatibility mode to 1.1 which makes the implicit Variant conversion possible. Take a look into Spring.inc to disable it and make the Nullable<T> completely type safe.