Repository navigation
[Request for feedback] Nullable types, null and undefined #7426
Description
Activity
- addedDiscussionIssues which may not have code impactIssues which may not have code impact
on Mar 7, 2016 I thought that
nullable FoomeantFoo | null | undefinedi.e. bothnullandundefinedare considered valid nulls.Belief basis:
From #7140

That PR has instances in the discussion where it means only undefined and sometimes both null and undefined. I think Nullable should mean one of these consistently everywhere. Sorry if I've misread 🌹.
RyanCavanaugh commented
on Mar 8, 2016 MemberMore actionsJust to explore what this means
Option 1:
T?=T | undefinedThis is an argument for
T?being exactly equal toT | undefined.Consider some code:
declare function f(x?: number): void; declare function g(x: number?): void;
Should
fandgbe identical in behavior when invoked with one argument ? Intuition seems to point to yes. Ifnullwere in the domain forxing, then these functions would not be equivalent, which is potentially very confusing.Now let's think of the implementation of
g:function g(x: number?) { if (g === undefined) { // do something } else { // Safe to invoke a method on `g` ? console.log(g.toFixed()); } }
Is this code correct? Intuition, at least among people who don't use
null, says "yes".Similarly, for types:
interface Thing { height?: number; weight: number?; } let x: Thing = ...; let h = x.height; let w = x.weight;
If
nullwere in the domain ofnumber?, thenhcould beundefinedornumber, butwcould beundefined,number, ornull. Again, all things equal, symmetry is very preferred here.Pros
- Preserves symmetry between
(x: number?) => voidand(x?: number) => void - Makes things very smooth for programs that don't use
null undefinedis the only true "missing" value in JavaScript (modulo some obscure DOM APIs)
Cons
- Makes things more awkward for programs that use
nullas a sentinel non-value
Option 2:
T?=T | undefined | nullThis is an argument for
T?being exactly equal toT | undefined | null.Consider some code:
function f(): number? { return Math.random() > 0.5 ? 32 : null; } const x = f(); Debug.assert(x !== undefined);
Is this code correct? It certainly looks like it. But if
T?isT | undefined, then this program has two errors -fhas an incorrectreturn nullcodepath, andxcan't be compared withundefined.Also consider this code:
interface Thing { name?: string; value: string?; } // OK var x: Thing = { value: null }; // Also OK var y: Thing = { name: undefined, value: null }; // Error var z: Thing = { name: null, value: null };
Again, at inspection, this program looks good.
Pros
- Makes it easy to write programs that use
null
Cons
- Complete lack of symmetry between
(x: number?) => voidand(x? number) => void
Option 1a: Include
T??=T | undefined | nullIf
T?=T | undefined, then we might want an alias forT | undefined | null; given available punctuation,??seems like the best choice (T~andT*were mentioned but considered generally worse).Pros
- Convenient
- Generally easy to understand
- Much shorter than
Nullable<T>
Cons
T??looks somewhat silly, at least for nowT??isn't the same as(T?)?- Maybe no one uses this and it's a waste of effort / complexity
Reacted by GoToLoop- Preserves symmetry between
RyanCavanaugh commented
on Mar 8, 2016 MemberMore actionsBasarat Ali Syed (@basarat) don't take the language in the PR as gospel -- earlier comments came before we actually tried out that behavior in the compiler (which, as a data point, does not use
null)mhegazy commented
on Mar 8, 2016 ContributorAuthorMore actionsAs Ryan Cavanaugh (@RyanCavanaugh), this issue is to illicit feedback to put in #7140; so #7140 should not be used as the expected behavior yet.
My vote: Allowing
nullto be a valid nullable akaT | null | undefinedReasons:
nullis used quite a lot in node forerrorargument to callbacks.nullis in the name of nullable- flow treats
nullandundefinedthe same : http://flowtype.org/docs/nullable-types.html
Ryan Cavanaugh (@RyanCavanaugh) looking back I did originally misread the question here 🌹 :)
Reacted by GoToLoop, Marijn Haverbeke, Justin Bowes, Denis Lepyohin and KaelFrom a developer ergonomics perspective, 99% of the time I care about nullability it's because I want to know "is it safe to call
foo.bar()on foo?"So for me it comes down to: will the nullable type support help the compiler prevent me from doing the following mistake?
let x = document.getElementById('nonexistent-id'); x.innerText = 'hello';
Unfortunately, it appears that x is
nullin the above, so I'm not sure how we can typegetElementByIdunless it has return typeHTMLElement?and that type must includenull. From that perspective null and undefined should be treated the same.Finally, based on my "99% of use cases" metric, the proposal variant involving ?? makes the language a lot uglier for not a proportional amount of gain.
Regarding optionality and confusingness (the symmetry arguments), I don't mind asymmetries because the two question marks in
foo(x?: number?)mean totally different things to me. The first one is a modifier on the function itself -- note that it modifies the arity of the function -- and the second is a statement about the type of x (e.g. it could just as well be replaced with a typedef). Perhaps it's just because I've looked at too much closure code, which uses = instead of ? for optional arguments though!Reacted by GoToLoop, Yury and Ernesto GarcíaAnother use case to consider:
let map: {[key: string]: MyObject?} = ...; let x = map['foo']; x.bar();
I vote for option 1 as described by Mohamed Hegazy (@mhegazy) but I'd like a clarification.
Are there any problems with the lack of symmetry between
(x: number?) => voidand(x?: number) => void? I interpret the two types very differently i.e. one is a function that takes a mandatory argument that may be null or undefined, the other is a function that can take an optional argument. They also read very clearly to me:- definite x, maybe number maybe
null | undefined - maybe x, if provided, definitely number.
There is a 3rd signature: (x?: number?) which would be
- maybe x, and if provided, maybe number, maybe not.
And finally we add the following quirk of JavaScript: passing
undefinedshould be indistinguishable from missing values. * This is consistent with standard JS semantics when accessing arguments and object properties directly. Now we get:x:number?- x is number or null or left outx?:number- x is number or left out.
Thats the intuition, anyways. This also makes it possible to model functions that only really expect optional arguments but never nulls. Unfortunately, this would mean some type definitions may need to be updated to allow for nulls.
Its hard to say whether
x?:numbershould be different fromx:number?. My rule of thumb guess is that it shouldn't be, because most missing argument/field checks are super-sloppy i.e.if (!x), better ones are still sloppyif (x != null)(common enough to be now included as a special case exception in JavaScript standard style) and many developers learn to avoid using the nameundefined, as it can be shadowed.On the other hand, I imagine that old style
typeof x === 'undefined'checks are still quite common. In my experience however, they were often considered bad precisely because they fail to account for nulls. Instead this alternative was normally recommended:x !== null && typeof x !== 'undefined'which we now consider unnecessarily verbose as its exactly the same asx != nullIt would probably be a good idea to look at some utility libraries (lodash, jquery, etc) and see what kind of checking functions they provide, as well as how they are used in the wild
(*) of course its distinguishable, however in practice most code doesn't or shouldn't make a distinction. Unfortunately I cannot remember the esdiscuss thread that argued this...
- definite x, maybe number maybe
In case someone finds this useful there's a similar proposal for the dart language here (Non-null Types and Non-null By Default (NNBD)).
It includes a comparison to null types in other languages.
I prefer
number?as a shorthand fornumber | undefinedrather thannumber | undefined | nullbecause it composes better (you can addnull, it's impossible to subtract it), and serves as a better indicator of how most builtins function. (I'd rather be sprinklingnumber?'s everywhere rather thannumber | undefined, that's for sure!). So many builtin structures in JS (object indexes, for one!) only ever introduceundefinedand nevernull- having to write that out by hand each time seems comparably tedious.In any case, having base types which include neither value lets me create more accurate typings, this is just a contest for shorthand syntax.
T?andT??look both short and easy to work with, so I'm all for them, but I'll easily write outtype Nullable<T> = T | null;andtype Undefinable<T> = T | undefined;in my own code, if need be. And I will treat them separately because I don't want to assign something nullable to something undefinable, since then it could get anull, which would be bad.Reacted by GoToLoop and Denis LepyohinRyanCavanaugh commented
on Mar 8, 2016 MemberMore actionsTL;DR: Flow was right,
T?should beT | null. Seeking symmetry aroundx?: numberandx: number?is a trap.Let me put forth a new option: Unifying around the idea that
undefinedis the missing value andnullis the non-value, with the logical outcome being thatT?is actuallyT | nullandT??isT | undefined | nullMotivating example 1: Functions
function fn(x: number?) { /* implementation here */ } fn(undefined); // OK fn(null); // Error fn(); // Error
This is very much wrong. Outside of dodgy stuff like checking
arguments.length, the implementation offncannot distinguishfn(undefined)fromfn(), but we're saying the latter is an error and the former is OK. And at the same time, we're saying thatfn(null)is wrong even thoughfntried to say that it could accept anumber?, which raises the question of what we even call this kind of type since "nullable" is clearly off the table.Let's consider instead that
undefinedmeans "missing".function fn(x: number?) { /* implementation here */ } fn(undefined); // Error, x is missing fn(null); // OK fn(); // Error, x is missing
Now the cases where
fncan distinguish what got passed to it actually correspond to what the compiler would consider valid. A 1:1 correspondence between runtime and compile-time behavior should be a good sign.Then consider the optional parameter case:
// Callers see this as (x?: number) => void. // *Not* equivalent to (x: number?) => void function fn(x = 4) { // No need for null/undef checks here // since x can't be null console.log(x.toFixed()); } fn(undefined); // Allowed fn(null); // Error, can't assign null to number fn(); // Allowed
This behavior is the same as Option 1 above, except that we don't need to think about how to introduce
nullback into the type system.Combining the two:
function fn(x: number? = 4) { // Must check for 'null' in this body if(x === null) { console.log('nope'); } else { console.log(x.toFixed()); } } // All OK, of course fn(); fn(null); fn(3); fn(undefined);
Motivating Example 2: Objects
interface Point { x: number?; } var a: Point = { x: undefined };
Should this be legal? If we asked a JS dev to code up an
isPointfunction, they would probably write:function isPoint(a: Point) { // Check for 'x' property return a.x !== undefined; }
Again, by saying
T?isT | undefined, we've diverged the type system behavior from the runtime behavior as commonly seen in practice. Not good.Separating concerns and
T??
JavaScript code has two concerns to deal with:- Does this property exist (or for variables, is it initialized) ?
- Does this property have a value ?
The first concern, existence or initialization, is checked by using
=== undefined. This tells if you if a property exists or a variable has been initialized. "Present according tohasOwnProperty, but has the valueundefined" is not a state that the vast majority of JS code recognizes as being meaningful.The second concern, having a value, is conditional on existence/initialization. Only things that exist and aren't
nullhave a value.It's a mistake to try to merge these concepts into one unified thing. See the section on
undefinedas sentinel for more comments on this.For an arbitrary
x, there are four states we can be in:xhas a value (T)xmight be missing, but if it's not missing, it has a value (T | undefined)xis not missing, but might not have a value (T | null)xmight be missing, and if not missing, might not have a value (T | undefined | null)
Given this:
- Clearly
Tis state 1 (T) - Clearly
T??is state 4 (T | undefined | null) - Unclear: Is
T?state 2 (T | undefined) or state 3 (`T | null)?
We can answer this by writing two declarations:
function alpha(x?: number) { } function beta(x: number?) { }
If we believe in separation of concerns, as I think we should, then:
alphacorresponds to state 2,T | undefined. Whenxis not missing, it has a value.betacorresponds to state 3,T | null.xis not missing, but may not have a value.
In other words,
x: number?isnumber | null.x?: numberisnumber | undefined.undefinedas sentinel and the pit of success
Our informal twitter poll showed ~80% of people usingnullin some regard. I think this makes a lot of sense and we shouldn't steer people away from that pattern based on our bias of how the compiler happens to be implemented.One thing to notice in the compiler implementation is that we actually have lots of non-values that are implemented as sentinel references that could have been
nullinstead, and vice versa. For example,unknownTypeandunknownSymbolare, in the vast majority of cases, simply checked for reference identity the same way we would fornullif that keyword were allowed in our codebase. Indeed, failing to check forunknownSymbolis a common source of bugs that we could avoid if we had anull-checking type system and used thenullsymbol to mean unknown (thus ensuring that all necessary code guarded against it).Conversely, we use
undefinedas a sentinel in dangerous ways. An informative thing to do is look at where in our codebase we writeexpr === undefinedwhereexpris a bare identifier (not a property access expression, where we're usually checking for an uninitialized (i.e. missing!) field). In nearly every case, we're usingundefinedas a sentinel value, in which case the choice ofnull/undefined/ reference sentinel is immaterial, and arguably poorly chosen.For example, this code uses
undefinedas a sentinel (comment is as written):const arg = getEffectiveArgument(node, args, i); // If the effective argument is 'undefined', then it is an argument that is present but is synthetic. if (arg === undefined || arg.kind !== SyntaxKind.OmittedExpression) {
The implementation is:
function getEffectiveArgument(node: CallLikeExpression, args: Expression[], argIndex: number) { // For a decorator or the first argument of a tagged template expression we return undefined. if (node.kind === SyntaxKind.Decorator || (argIndex === 0 && node.kind === SyntaxKind.TaggedTemplateExpression)) { return undefined; } return args[argIndex]; }
Where's the bounds checking in that code? It's in the calling code:
const argCount = getEffectiveArgumentCount(node, args, signature); for (let i = 0; i < argCount; i++) { const arg = getEffectiveArgument(node, args, i);
How do we know that
getEffectiveArgumentCountandgetEffectiveArgumentagree in implementation? They're implemented over 200 lines apart. If we go out-of-bounds atreturn args[argIndex];, the manifestation is going to be very, very subtle. We could have allocated aExpressionsyntheticArgumentsentinel and checked for that instead, turning out-of-bounds errors into immediate crashes. Alternatively, we could have usednullto indicate that the argument is synthetic. Usingundefinedhere seems simply dogmatic -- two safer values were available, but not used. The fact that we generally get away with this sort of thing shouldn't be seen as a positive case for usingundefinedas a universal sentinel.Reacted by Jesse Schalken, Maxime Quandalle, jods, Zalim Bashorov, GoToLoop, Mike Lippert, Nabil Redmann, megadonkey, David Rodrigue, John Landheer and 3 morejesseschalken commented
on Mar 8, 2016 ContributorMore actionsRyan Cavanaugh (@RyanCavanaugh) That makes a great deal of sense. 👍 (
nullfor things that are nullable.undefinedfor things that may not exist.)Also, from a purely DX perspective, if
nullandundefinedare conflated (egbar: string?===bar?: string, or the former overlaps the latter), it is easy to accidentally omit properties or arguments just because they happen to be nullable. Eg:function func1(params: {foo: string, bar: string?}) { // ... } function func2(obj: Thing) { let foo = obj.getFoo() let bar = obj.getBar() // ... use foo and bar ... func1({ foo: foo // oops, forgot to pass bar }) }
TL;DR: Flow was right, T? should be T | null. Seeking symmetry around x?: number and x: number? is a trap.
The page Basarat Ali Syed (@basarat) linked to (http://flowtype.org/docs/nullable-types.html) says Flow considers
?Teffectively asT | null | undefined. Is that right?Ryan Cavanaugh (@RyanCavanaugh) This proposal makes sense to me. Feels like the best we can do in a language that supports both
nullandundefinedas distinct but absent values...One clarification on object properties... let's take an interface...
interface Foo { w: string?, x?: string, y?: string?, z: string?? }
Are these assumptions correct?
#0 (can't get Markdown to play nice here) This code can also be written as:
interface Foo { w: string | null, x: string | undefined, y: string | null | undefined, z: string | null | undefined }
yandzare the same type and behave the same way{w: null}is assignable toFoo, omitting bothxandywhich use the existing "optional" property syntax, andzwhich uses the new "super-nullable" (for lack of a better name) syntax- There is a distinction between optional (or "undefinable") properties like
x?: stringand nullable properties likex: string?. {}is not assignable toFoo, it's missingw. Similarly, neither is{x: "bar"}.
is this so important to come up with the consistent syntax? let's face it there are 3 cases to address:
- T | null
- T | undefined
- T | null | undefined
and then what about this:
- null | undefined
can we just keep it the way it is:
- to keep it consistent with the current syntax and semantics, let's leave optional values
value?: Tthe way the areT | null | undefined - for everything else let's not make up any more syntax and use the union types or aliases:
var value: string | null | undefined;ortype Nullable<T> = T | null;
if you ask me the
var value: T | null | undefinedis as clear as a day, whereasvar value? : T??is what I type when I am hungoverthe argument that
T | null | undefinedis longer to type is faint whoever wants can make it as short astype u = undefined; type n = null; var value: T | u | n; // <-- fixed! // or type un = undefined | null; var value: T | un;Reacted by GoToLoop136 remaining items
- added a commit that references this issue
on May 4, 2016 aluanhaddad commented
on Jun 24, 2016 ContributorMore actionstypeof null === 'object'
as Douglas Crockford noted, this is plain wrong. Elaborating, he alluded to a dialog with Brendan Eich where the latter stated that this was a bug that was never fixed (citation not provided).
That is definitely a reason not to use
null.
Furthermore, for everyone advocating for the use of explicitnullfor indicating an optional value, it is worth considering that, just as most languages do not have bothnullandundefined, most languages do not define both anOption<T>and aMaybe<T>type in their standard library. Haskell usesMaybe, Scala usesOption, but they are both expressing the same thing.That is definitely a reason not to use
null.Definitely? That's radical and it seems many people use
nullnonetheless. Yes this is unfortunate. Covariant arrays in C# are unfortunate as well. As much as I would like to see both of those reversed, they are not enough to stop me usingnullin JS or arrays in C#.BTW you will not be able to completely evade
null, as some APIs (even built-in ones) return it.Haskell and Scala (and most languages) don't have both a
nullandundefinedequivalents because AFAIK they are not dynamic languages.
undefinedexists to represent the fact that (a)x.frobmay not even exist, which is different from (b) existing but being empty akanull.
Since you want to compare with static languages, in Java (a) is a compilation error and (b) isnull. So yes, both exist, in a way.aluanhaddad commented
on Jun 26, 2016 ContributorMore actionsjods (@jods4) I disagree, the absence of a property does not indicate the need for a separate value because the equivalent of a JavaScript object in a language like Java is a hashtable that returns
nullinstead of throwing an error when an attempt is made to retrieve of value for a non-existent key. Furthermore, JavaScript uses undefined in almost all the places where most languages would use null. It has nothing to do with Dynamic typing at all, it is actually about the semantics of the "no such member" case.JavaScript object is a hashtable that returns null instead of throwing an error
*typo (I think). You meant to say
undefined. I agree with you. Crockford agrees with you. Its undefined all the way. But I just havenullinconsistently because of dealing withdom/regex/ nodejs :) So I end up with both :-/. But never check explicitly against one ... just== nullor== undefined(either works for both) 🌹Reacted by Aluan HaddadReacted by Aluan Haddadthe problem with both undefined and null is that in practice there might be
and often is more than 2 flavors of a value absence that all need to be
considered at the same time:- unspecified default parameter (undefined?)
- or specified but not found (null?)
- or specified as not applicable (now what?)
- or specified as not yet decided (um, help)
and worse comes to worst next time the same values camean completely
different thingsnow pardon me, what exactly are we discussing here?
On Jun 26, 2016 19:48, "Basarat Ali Syed" notifications@github.com wrote:JavaScript object is a hashtable that returns null instead of throwing an
error*typo (I think). You meant to say undefined. I agree with you. Crockford
agrees with you. Its undefined all the way. But I just have null
inconsistently because of dealing with dom / regex / nodejs :) So I end
up with both :-/. But never check explicitly against one ... just == null
or == undefined (either works for both) 🌹—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#7426 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AA5Pzd4Itjc6Dv7ack2i5m5cswfbyNdcks5qPw-1gaJpZM4HrL9y
.jesseschalken commented
on Jun 27, 2016 ContributorMore actionsstatic equivalent of a JavaScript object is a hashtable that returns null instead of throwing an error when an attempt is made to retrieve of value for a non-existent key
Consider if in a statically typed language you have a
Map<Maybe<string>>with a methodMap<T>.get(key:string):Maybe<T>. Then the result of calling.get()would be aMaybe<Maybe<string>>and you would deal with the result with something like:match (map.get('key')) { Nothing => echo "key doesn't exist" Just x => match (x) { Nothing => echo "NULL was stored in the map (meaning depends on the purpose of the map)" Just s => echo "Got a value: {s}" } }If
Maybeisn't an algebraic data type but is actually a union with a specialnullvalue, then theMaybe<Maybe<string>>becomesstring|null|null, and the two different nulls become indistinguishable:let val = map.get('key') if (val === null) { echo "key might not have existed, or it might have existed but been set to null, don't know :(" } else { // .... }This is why
undefinedexists. As soon as you (as a language designer) go down the path of the presence/absence of a property being indicated in the result of fetching it,undefinedbecomes necessary to distinguish that case from anullthat the developer has purposely placed there.nullis for developer use for things that are nullable.undefinedis for language use as the result for fetching things which don't even exist or haven't even been set. See here for an example of why the difference is important.When it comes to
typeof null === 'object', the reason for that is that JavaScript was originally intended to be "like Java" and in Java (and C/C++ for that matter) the only place you can find anullis somewhere you might otherwise find an object/pointer.nullwas originally intended to mirror a null reference or null pointer from other languages, and sonullwas really a special instance of the reference/pointer/"object" type. (In practice of course JavaScript is a dynamic language andnullstands by itself and can be used for whatever purpose you wish.)aluanhaddad commented
on Jun 29, 2016 ContributorMore actions@> *typo (I think). You meant to say undefined.
Basarat Ali Syed (@basarat) I actually did mean
nullbecause I was trying to map the concept of a JavaScript object in terms of a some arbitrary, statically typed language to point out that a JavaScript object would be modeled as an associative collection type. But thank you ❤️My point was exactly what you went on to say
But I just have null inconsistently because of dealing with dom / regex / nodejs :) So I end up with both :-/. But never check explicitly against one ... just == null or == undefined (either works for both)
Exactly this. One shouldn't need to care. It's unfortunate that we have to deal with both, but the distinction between them is not semantically significant, and the argument that a dynamic language needs both is erroneous.
When it comes to typeof null === 'object', the reason for that is that JavaScript was originally intended to be "like Java" and in Java (and C/C++ for that matter) the only place you can find a null is somewhere you might otherwise find an object/pointer. null was originally intended to mirror a null reference or null pointer from other languages, and so null was really a special instance of the reference/pointer/"object" type.
That is wrong. The fact that
typeof null === 'objectwas a bug that cannot be fixed. It was not intended to be this way. How is an undefined reference different from a null reference?jesseschalken commented
on Jun 29, 2016 ContributorMore actionsThat is wrong. The fact that
typeof null === 'objectwas a bug that cannot be fixed. It was not intended to be this way. How is an undefined reference different from a null reference?See
- http://kiro.me/blog/typeof_null.html
- http://www.2ality.com/2013/10/typeof-null.html
- https://twitter.com/BrendanEich/status/652440848299372544
- https://twitter.com/BrendanEich/status/286600050023469056
- https://twitter.com/BrendanEich/status/330775086208524288
- https://twitter.com/BrendanEich/status/617450289889607681
- https://twitter.com/BrendanEich/status/157868556837584896
- https://twitter.com/BrendanEich/status/720863709103435780
Also note the distinctly different wording for
undefinedandnullin the ES spec (emphasis mine):4.3.10 undefined value
primitive value used when a variable has not been assigned a value
4.3.12 null value
primitive value that represents the intentional absence of any object value
The original tagged union for a JavaScript value had
undefinedas a distinct tag and an "object" tag with a pointer to an object.nullwas an instance of the "object" tag with a null pointer.nullserved the purpose of mirroring the null reference from Java, whileundefinedserved the purpose as the value of things which don't exist/haven't been set.This way you could, say, take a Java object and ask if a property exists with
=== undefined, and ask whether a property whose type is an object if it contains null with=== null. Those are two different operations.typeofsimply queried the type tag, sotypeofon a Java property of type object would always return"object", regardless of whether the reference inside happened to benull.aluanhaddad commented
on Jul 1, 2016 ContributorMore actionsJesse Schalken (@jesseschalken) I know the behaviour is well specified and standardized, I'm saying it's semantically wrong for null to have type of object.
See the remarks by Brendan Eich in this thread
http://wiki.ecmascript.org/doku.php?id=discussion:typeof
Actually found my way there by following some of the links you provided which were quite interesting.I'm using TS 2.0.7 and trying to understand how this was implemented. It seems that in TS 2.0.7, if I write:
interface Foo { name?: string; }Then the type of
nameisstring | undefined. Is this correct? I ask because I can then write:let foo: Foo = {}; // OkHowever, if I do this:
interface Bar { name: string | undefined; // Same type, no? }The I get this:
let bar: Bar = {}; // Error: missing field nameThis kind of sucks because a) it seems inconsistent because something besides the type is deciding whether it can be left out and b) if it worked I could do this:
interface Foo<T> { name: string | T; } type PartialFoo = Foo<undefined>; type CompleteFoo = Foo<never>;I dug through several threads, but I didn't see any reason why
name: string | undefinedshould have different semantics thanname?: string.Thanks.
Michael Tiller (@xogeny)
There is a slight difference and it applies equally to optional parameters and optional members.
The?notation is not just a shortcut forT | undefined. It actually means that the parameter/member is optional and this is a distinct concept.If you declare
function x(n?: number) {}
Then the parameters
nis optional and you can call the function withx().
Of course in that case,nwould beundefinedin the function body, which is why its type definition is extended tonumber | undefined. But this rather a side-effect of optionality.
Writingfunction x(n?: number | undefined)is legal and would be strictly equivalent.On the other hand, if you declare
function x(n: number | undefined) {}
Then the parameter is not optional and you have to pass it, even when undefined, like so:
x(undefined).
Callingx()is not legal in that case.The optionality part can be seen more clearly if you try to add more parameters. The following declaration is illegal:
function x(a?: number, b: number)because all parameters after the first optional parameter have to be optional as well.It works the same for optional members.
I agree with you that{ x?: number }and{ x: number | undefined }are mostly equivalent, but strictly speaking, there are differences. Consider the following examples:'x' in { x: undefined } === true; 'x' in { } === false; Object.assign({x: 3}, {x: undefined}) // == {x: undefined } Object.assign({x: 3}, { }) // == {x: 3}
Reacted by DecadeMoon, Aluan Haddad, John Landheer, A. Matías Quezada and Mike Joycemhegazy commented
on Nov 16, 2016 ContributorAuthorMore actionsvoidonly makes sense in the return type position of a function to denote the "absence of a value". Before TS 2.0, ppl have usedvoidto meanundefinedfor values, which is close, but not correct. So for your example,Voidable<T>should be reallyT | undefined.- locked and limited conversation to collaborators
on Jun 19, 2018
With the work on Nullable types in #7140, we would like to field some user input on the current design proposal.
First some background:
nullandundefinedJavaScript has two ways that developers use today to denote
uninitializedorno-value. the two behave differently. where as null is completely left to user choice, there is no way of opting out ofundefined, so:awill always implicitly hasundefined, and so will the return type ofbar.Nullability
Given the JS semantics outlined above, what does a nullable type
T?mean:T | null | undefinedT | undefined1.
T | null | undefinedIt is rather subtle what
?means in different contexts:2.
T | undefinedThis is more consistent, the
?always means to| undefined; no confusion here.T??can be used to meanT | undefined | nullorT? | null