"use restrict"; // restrict mode for JavaScript

Restrict mode for JavaScript (ECMAScript) is a proper subset of the language. Conform to it and enjoy more robust programs.

The case for restrict mode (announcement)

Discuss restrict mode in the JSShaper google group

Try restrict mode directly in your browser

Restrict mode is simple to learn. The rules are:

  1. === and !==
    are already strict by default, with identical semantics in restrict mode. Use them! click for why?
    Because === and !== are consistent, you'll remember their semantics, and most of the time they're exactly what you wanted. The loose == and != operators are quite complex and their semantics are not very consistent, but they are still available in restrict mode. The use of the built-in strict operators is a common practice today. It's a pity there's just two of them. But we're fixing that now.

  2. < <= >= >
    are restricted to primitive strings or numbers (but never a mix) click for why?
    Because < >= >= > either compares numbers or strings (lexically). Mixing these types yields unrobust programs. Relying on automatic coersion from other types may surprise you. Use the String() or Number() functions (without new) to type-convert explicitly, when you need to.

    • Most languages that support comparing of arrays does so element-wise. JS coerces into strings, which often is not what you want. [1] < [2], [1,2] < [1,3], but [10] < [2] (coerced into "10" < "2")
    • JS has primitive and boxed types, with inconsistent semantics.
      var x = new Number(3), y = new Number(3); x <= y, x >= y, 3 == x, 3 == y, but x != y. The first comparisions coerce to primitives, the last compares object identity.
    • Mixing numbers and strings may surprise you, especially so when mixed by mistake: 01 < 1 === false, "01" < 1 === false but "01" < "1" === true. The first two compare (coerced) numbers, the last strings (lexically).

  3. +
    is restricted to primitive strings and numbers (in any combination) click for why?
    Because type coercion for + is such a common source of bugs. a + b either adds numbers or concatenates strings. Relying on automatic coersion from other types may surprise you. Use the String() or Number() functions (without new) to type-convert explicitly, when you need to. For string formatting, you're often better off using Fmt() or something similar.

    • Many dynamic languages support array concatenation using +. JS coerces into strings and then concatenates those which is confusing and almost never what you want: [1, 2] + [3, 4] yields "1,23,4".
    • num + undefined or str + undefined is rarely what you intended so an informative error is more useful than a bogus NaN or "...undefined" value.
    • The semantics for nostr_nonum + nostr_nonum are complex and error prone. What's the result of obj1 + obj2?
    • toString() and valueOf() ordering may surprise you: "object is " + {toString: function() { return "str"; }, valueOf: function() { return 1; }} yields "object is 1".
    • Date is not funny: (new Date()) + 1 === "Tue Dec 28 2010 23:59:53 GMT+0100 (CET)1" but (new Date()) - 1 === 1293577193075. It coerces to string or number depending on operator. Use ES5 Date.now() when you just need the timestamp.

    Restrict mode allows coercing string concatenation via num + str (and str + num) for pragmatic reasons. It happens to be the most common coercing usage out there in the wild. Allowing this is a trade-off between slighly increased complexity vs lowering the barrier of restrict mode adoption.

    The same rule applies to +=.

  4. - * / % & | ^ ~v << >> >>> -v ++v v++ --v v--
    are restricted to primitive numbers click for why?
    Because they only operate on numbers, per definition. Relying on automatic coersion from other types may surprise you. Use the Number() function to type-convert explicitly, when you need to.

    • null * 2 === 0 but undefined * 2 === NaN. Both cases are common sources of bugs. That they yield different results is interesting in itself since JS defines null == undefined.
    • All JS objects can have valueOf and toString functions. Both must return primitive values (but not necessarily number or string). The order of how these are invoked is not obvious: "o: " + {valueOf: function() { return 3; }, toString: function() { return "4"; }} === "o: 3". In practice, valueOf isn't used much other than for automatic unboxing of Booleans, Numbers and Strings. For the rare times when you use boxed types you can easily convert them to primitives explicitly. It's crucial that you know when you use boxed values (and limit their usage), since their semantics are sometimes surprising. Such as: Boolean(new Boolean(false)) === true.

    The same rule applies to -= *= /= %= &= |= ^= ~= <<= >>= >>>=.

  5. That's it!
    +v !v && || ?: o[k] and all other operators have identical semantics in restrict mode click for why?
    Because I believe they are well defined. +v (unary plus) is defined to type-convert into number, but you should consider using Number(v) instead. !v is defined to type-convert into boolean, and then inverts it. && || ?: compares against falsy values (so does the if-statement and others). o[k] (index) converts its key to string. And so on. I could have missed out on something that should be restricted, please let me know in that case.
    • I am considering restricting typeof, such that typeof null is disallowed. Let me know if you have an opinion on that.

Get started with restrict mode by downloading JSShaper, a JS/ES source transforming framework. It includes the restricter plugin, which adds checking to your program without destroying your formatting. Run your tests on the output of restricter. Since restrict mode is a proper subset of JS, and conversely JS is a proper superset of restrict mode, your restrict mode conforming programs works fine in any JS engine. Only that by running your program through restricter now and then, it becomes more robust. click for instructions

git clone git://github.com/olov/jsshaper.git
cd jsshaper
git submodule init
git submodule update
cd src
node run-restricter.js myfile.js > myfile-checked.js

# then include plugins/restricter/restrict-mode.js in your project

Like ES5 strict mode, restrict mode is opt-in. Similarly, you enable it by placing "use restrict"; first in your program or function. There's always the option to bail out: (/*@loose*/ "1"+2+3).

In fact, chances are that you are already mostly conforming to restrict mode, except that you may use str + nostr_nonum to perform string conversion and concatenation at the same time. You may find that replacing that with Fmt() or String() calls here and there is easy. You may also find that restrict mode conforming programs are easier to read and reason about.

You're encouraged to "use strict"; "use restrict";

"I know my JS, why would I need this?"

This isn't about fixing you, it's about fixing the language. Even the best make mistakes due to loose semantics. If you're not convinced yet about loose operator complexity, take a look at this implementation of EcmaScript operators in EcmaScript. Also note that SpiderMonkey, JSC and V8 does not implement identical semantics. Since restrict mode is opt-in, you may consider adding it just to a part of your program.

About

Restrict mode was created by Olov Lassus and isn't related to the work by the ECMA committee. ES5 strict mode was important in that it started improving the language by reducing it, not just adding to it. Restrict mode, just like ES5 strict mode, is something you can start using today thanks to JSShaper. It's technically possible, but not very likely, that a JS engine could add built-in support for restrict mode in the future. It doesn't really matter.

Restrict mode is a moving target and details are likely to change based on your feedback. Thanks for trying it out!

Follow me on Twitter