Requiring `var` for variable declarations

We’re making a fairly major change in Mojo syntax leading up to 1.0: var will now be required before new variable declarations. Previously, you could declare a new variable either through the use of an explicit var variable = ... or through variable = .... We will start warning about the use of the latter in the next nightly, and then make it an error to use that syntax before 1.0.

The two ways of declaring a new variable in Mojo have been the subject of lengthy prior discussions and we know that changing this behavior will be controversial. In general, for Mojo we have tried to adhere to Python syntax as much as possible, only deviating when there is a good reason to do so.

In practice, many of us have observed subtle issues with the use of non-var variable declarations (silent variable redefinitions on typos, issues with variable scope, etc.). As a conservative position before 1.0, we are going to restrict behavior such that var is required for new variable declaration. This gives us the option of revisiting bringing back this behavior as an additive change post-1.0, which would preserve source compatibility with 1.0 if we chose to do so.

One specific pattern I’ll call out as part of this change is that code like the following, which used to work:

if c:
    x = 1
else:
    x = 2
use(x)

we now encourage to be written as:

var x: Int
if c:
    x = 1
else:
    x = 2
use(x)

We understand that many people liked the flexibility and Python-like nature of being able to drop var keywords for variable declarations. This isn’t a decision we took lightly, but we feel the safest option is to have only one way to declare new variables in 1.0 and take the time to see if we could resolve the issues we found with more implicit variable declarations. Again, you’ll start seeing warnings on non-var variable declarations in the next nightly to give you time to migrate your code before the official 1.0 release.

Implicit var was interesting, although the inconsistency with for loop binding and technically except bindings was noticeable. Type inference in if and with statements was probably the biggest advantage. One option would be to add an explicit function scoped variable declaration binding like defvar/local.

Yes! Excellent. Love this. =)

Concerning this example, there is still a simpler way to write it in Mojo (a ternary expression):

var x = 1 if c else 2
use(x)

But if there are more statements inside the if clause, then the first syntax must be used.

var result = (
    value_a if condition_a
    else value_b if condition_b
    else value_c if condition_c
    else default_value

var result = value_a if condition_a \
    else value_b if condition_b \
    else value_c if condition_c \
    else default_value

)

Technically, you can chain ternaries. I wonder why this is only prevalent in other languages. Rust does not have this problem since everything is an expression. Thus, type inference does not feel useless.
For example, there is no technical reason why try couldn’t exist as an expression, provided it is parenthesized, similar to assignment expressions with the walrus operator (:=). By restricting the inline syntax to an atom enclosed in parentheses, like (try: expr1 except Exception: expr2), the parser eliminates all boundary, colon, and precedence ambiguities without colliding with top-level try statements.
This would be an interesting case for match expressions, since the same rules would apply.