Why use a dot?

Both options for [] have advantages.

e1?[e2] :

Follows C# and Swift

Is concise

Is similar to the syntax for the ! operator (which you’ll be able to add after an expression to say that its value isn’t null, even though it has a nullable type): e1![e2]

e1?.[e2] :

Is similar to cascade syntax:

e1..[e2] // cascade syntax

e1?..[e2] // null-aware cascade syntax

Is similar to other null-aware method syntax: e1?.e2()

Extends naturally to other operators

Avoids the ambiguity in the following code: { e1 ? [e2] : e3 }

We spent a while trying to come up with ways to avoid the ambiguity of ?[ . The problem is that you can’t tell whether code like { e1 ? [e2] : e3 } is a set literal containing the result of a conditional expression, or a map literal containing the result of a null-aware subscript.

If we add parentheses to make that code unambiguous, we could either choose to add them around the entire expression — { (e1 ? [e2] : e3) } — making it unambiguously a set literal. Or we could add them around the first part — { (e1 ? [e2]) : e3 } — making it unambiguously a map literal. But in the absence of parentheses, the parser doesn’t know what to do.

There are various solutions to this ambiguity, but none of them seem very satisfying. One approach is to rely on whitespace to distinguish between the options. In this approach, you always treat e1 ? [e2] as the start of a conditional expression, because there is a space between the ? and the [ . And you always treat e1?[e2] as a null-aware subscript because there’s no space between those two tokens. But relying on whitespace can really harm the user experience.

In theory, relying on whitespace isn’t a problem in formatted code. But many users write unformatted Dart code as an input to the formatter. And that input format would thus become more whitespace sensitive and brittle in this corner of the language. So far, those kind of corners are very rare in Dart, which is a nice feature. (One such corner is that — — a and --a are both valid but mean different things.)

Ignoring the ambiguity, there’s another reason for using the dot: if we add null-aware forms for other operators — e1?.+(e2) , etc. — we’ll probably want to require the dot, in which case requiring it for subscript is consistent with that future.

Another addition we’ve discussed for NNBD is a null-aware call syntax. If we don’t require a dot there, it has the exact same ambiguity problem:

var wat = { e1?(e2):e3 }; // Map or set?

Whatever fix we come up with for the ?[ , we’ll also have to apply to ?( .

Finally, consider this example of chaining the subscript:

someJson?[recipes]?[2]?[ingredients]?[pepper]

To our eyes, that doesn’t look very good. It scans less like a method chain and more like some combination of infix operators — a little like ?? . Compare it to this code:

someJson?.[recipes]?.[2]?.[ingredients]?.[pepper]

Here, it’s more clearly a method chain. Communicating that visually is important too, because users need to quickly understand how much of an expression will get null-short-circuited.

Putting all of that together, it seems like we should use the ?.[ form for the following reasons: