That sounds like an Option type to me:
String? :: Option
String :: Option.Some
null :: Option.None String? :: Option
String :: Option.Some
null :: Option.NoneThis interacts terribly with generics, since it means that only one side of the generic boundary can "own" the null value at any one time. To simplify a case where this can cause issues:
class Loadable<T>(
// null until value is loaded
var valueIfLoaded: T?,
)
// returns null if user is not found
fun getUser(id: String): Loadable<User?>
Code that interprets Loadable<T> will get stuck showing a loading bar, since it has no idea about getUser using null for anything else. This doesn't even generate a warning!The "correct thing to do here would be to add a `T: Any` bound on Loadable, which prevents T from itself being a nullable type. That would notify the author of getUser that they need to box the User?, so that the cases are kept separate:
data class Box<T>(val value: T)
class Loadable<T>(
// null until value is loaded
var valueIfLoaded: T?,
)
// returns null if user is not found
fun getUser(id: String): Loadable<Box<T?>>
These are things you don't even have to consider when using a typical well-designed Option type (which, mind you, could still have a syntax sugar for T? if you wanted it).