Description
Deep in the discussion of non-nullable types, there was a new, more practical, suggestion (credit particularly to @tejacques) of voidable types that are protected against unsafe access, while maintaining backwards compatibility. I think the suggestion is a bit too buried in that thread to be useful or noticed, so I'm creating a new issue for this proposal.
First, some definitions to make sure I'm on the same page as everyone else... for the purposes of this discussion, "void" refers to either a value of null
or undefined
, which will raise a TypeError
if you attempt to use it in certain ways, particularly accessing a member (e.g. foo.bar
) or calling it as a function (e.g. foo()
). Currently in TypeScript, all types are "voidable", by this definition.
The proposal is for a new modifier (I'll use a ?
prefix, similar to Flow's implementation, but it could be anything) that will make a type "void-protected". A void-protected type must be checked for existence before it can be used. For example:
function printShout(x: ?string): void {
console.log(x.toUpperCase()); // Error: x might be void
if (x) {
console.log(x.toUpperCase()); // legal
}
}
This does not affect assignments, for the sake of backwards compatibility:
function foo(x: ?string): void {
var y: string = x;
console.log(y.toUpperCase()); // Legal, but probably not a good idea
}
This is similar in philosophy to Java's Optional<>
type. While every type in Java is nullable, it has become standard to instead use Optional<>
to express this at an API level. While it doesn't completely protect against NullPointerException
s (aka the "billion dollar mistake"), it's a step in the right direction, and some IDEs will even log warnings if you assign null
to a normal, non-Optional
type.
In the case of TypeScript, a --noImplicitVoidable
flag could be added in the distant future, which would make all types non-voidable (that is, you could not assign null
or undefined
to them) by default. While this is a major breaking change, it can be mitigated by having "void-protected" types in the language for some time prior. That way, most code will already be using ?
prefixes wherever void types are expected.
Finally, under this proposal, there are basically four levels of voidability, which is more relevant if this hypothetical --noImplicitVoidable
flag comes to be. A lot of this has been adapted from @tejacques's response in the non-nullable
thread:
interface Foo {
w: string;
x: ?string;
y?: string;
z?: ?string;
}
// alternately
function foo(w: string, x: ?string, y?: string, z?: ?string): void
w: string
behaves exactly like it does currently: it must be explicitly set, but it can be void. Under --noImplicitVoidable
, it would be a strong guarantee of existence.
x: ?string
is new: it must be explicitly set, but can be void. As is, it behaves exactly like w
, but provides more safety for a consumer.
y?: string
continues to behave as-is. Under --noImplicitVoidable
, though, it would probably be treated as "undefinable" - that is, it may have a value of undefined
(implicitly, if it's not set) and accessing it must be guarded, but it would never have a value of null
.
z?: ?string
is equivalent to y
, but again provides more safety for consumers and would be preferred over y
in most new code.