Hoisting behaviour of 'const' and 'let'

Brendan Eich brendan at mozilla.com
Mon Oct 13 14:40:27 PDT 2008


On Oct 11, 2008, at 9:02 PM, David-Sarah Hopwood wrote:

> David-Sarah Hopwood wrote:
> [...]
>> What is proposed to be hoisted in the case of 'const' and 'let' is  
>> just
>> the point at which the variable's *scope* begins. But, unlike the  
>> case
>> of function definitions, this in general extends the scope to a point
>> where the variable has not been initialized. If 'const' and  
>> 'let' (with
>> an initializer) were not hoisted, then there could be a static  
>> guarantee
>> that the variable will have been initialized at every point at  
>> which it
>> is in scope. By hoisting the scope, we're effectively throwing away  
>> the
>> possibility of such a guarantee -- at least if we want to avoid a  
>> much
>> more complicated static analysis.
>
> This would also mean that we cannot have any types that do not include
> the value 'undefined' (either primitive types, or "non-nullable"  
> reference
> types).

An alternative implemented in some JS variants has a default value  
other than undefined, specific to the annotate type of the hoisted  
binding. This works well for scalar types. It leads to a value vs.  
reference type (struct vs. class in C#) distinction. We considered  
taking ES4 that far, but in the end decided not to go all the way,  
instead hardcoding the default value for each built-in primitive or  
"value type".

If we don't require let initializers then users will sometimes forget  
them, and use-before-set errors will happen -- unless we require  
analysis or a read barrier to make use-before-set an error. If we do  
not mandate analysis, then test coverage gaps will leave some such  
errors to be found in the field.

Meanwhile, real code on the web counts on var being default- 
initialized to undefined. Swimming upstream against this current will  
be hard. Because let (even with a use-before-set hazard) has other  
virtues over var, in order to make let the new var and encourage its  
widespread use, I do not think we should require let declarations to  
have initializers.

[Another point, mostly historical about ES4: changing let and const  
not to hoist (when we considered that choice) was not enough to avoid  
the "default value" problem in ES4. The ES4 class syntax left instance  
variables with non-nullable type annotations in need of default  
values, or else error on use-before-set (with attendant read barrier  
or static analysis costs). But the harmonized class syntax I proposed  
in Oslo avoids at least this case:

class Point(x, y) {
   let r = Math.sqrt(x*x + y*y);
   let theta = Math.atan2(y, x);
   . . .
}

by making the constructor head part of the class head, so the  
constructor parameters are in scope for evaluation of the constructor  
body, which is the class body.

There are other cases remaining where it's anywhere from awkward to  
painful to mandate initialization of every binding. Such hard cases  
can all be worked around at some level of discomfort, but var has no  
such mandatory initialization burden, so to keep let competitive as  
the new var, we passed on requiring initializers.]

My two cents: the argument for let and const not hoisting is strong  
enough on its merits, ignoring default value issues. The question of  
initial values for uninitialized type-annotated bindings does not  
weigh heavily in its favor, as it calls for type-specialized default  
value support.

/be


More information about the Es-discuss mailing list