Hoisting behaviour of 'const' and 'let'

David-Sarah Hopwood david.hopwood at industrial-designers.co.uk
Sat Oct 11 20:35:29 PDT 2008


Mark S. Miller wrote:
> On Sat, Oct 11, 2008 at 4:01 PM, Brendan Eich <brendan at mozilla.com> wrote:
>>> If we want to avoid the read-barrier, we should not hoist either const
>>> or let. If we are to consider not hoisting const, WE NEED TO DECIDE
>>> THIS NOW, before ES3.1 mandates a hoisting const.
>>
>> A few messages back you nicely repeated the
>> least-concepts/least-astonishment/most-symmetric case for hoisting to block
>> top that has carried so far. I don't see how we can backtrack here. We'll
>> flail hard.
>> [...other good stuff snipped...]
> 
> I'm convinced; thanks. I agree it's too late to consider non-hoisting
> const even if we come to regret it. Given that const hoists, let
> declarations must as well, and we can argue about let-read-barriers
> and repeated declarations later.

I disagree with this reasoning.

The reason for making 'const' hoist to the top of the enclosing block,
AFAIR, was consistency with function declarations. However, there are
good reasons why 'const'/'let' and function declarations should be
treated differently:

A function initialization -- that is, initializing the variable with its
function value -- never has side effects. A 'const' or 'let' initializer,
OTOH, can have side effects. Therefore, it is reasonable to hoist a
function declaration together with the corresponding initialization,
but it would be far too confusing to hoist a 'const' or 'let' initializer
(and no-one is proposing the latter).

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.

Hoisting to the top of the enclosing block is more useful for
function declarations than it is for 'const' and 'let'. A function
declaration may be quite long (in comparison to typical 'const' and
'let' initializers), and so it is more important to have some flexibility
in where to put it in order to make the code read well, which may require
that it be forward-referenced. Forward references are also useful for
mutually recursive functions.

In the case of 'function' it is desirable that the hoisting behaviour of a
function declaration within a block, be consistent with the ES3-specified
hoisting behaviour of a function declaration (using the same keyword) at
the top-level of the enclosing function or global scope. For 'const' and
'let', there is no existing behaviour specified by ES3 that we need to
be consistent with.

I agree that 'const' and 'let' should be consistent with *each other*,
but neither should hoist, IMHO -- their scope should only be the section
of the block after the declaration. In that case, we would have the
equivalence

  { ...; const foo = expr; ... }
<=>
  { ...; { const foo = expr; ... } }

and similarly for 'let'. This makes the meaning of multiple declarations
of the same variable identifier within a block perfectly straightforward,
even if they have different types.

-- 
David-Sarah Hopwood


More information about the Es-discuss mailing list