Both styles can be mixed, even inside one function. This can lead to confusion and render some guarantees impossible that “explicit var declarations” could provide. There is no guarantee that all variables are explicitly declared. Therefore it is always possible to (accidentally) introduce implicit variables that can lead to less readable code or even bugs.
Removing implicit variable declarations from Mojo altogether would be very controversial and is not in scope here.
The following alternatives for requiring explicit var declarations have already been proposed:
Provide a Mojo linter with a corresponding rule
Provide a compiler option
Don’t allow mixing both styles inside the same function
Maybe now is a good time to consider those alternatives before Mojo 1.0 is finalized.
Having used Python for a few years I can say with using Mojo I always use the var keyword with the declaration of the variable as this helps me to easily see in the code where I start to create the variables and what the type is.
Having explicit block scoped var declarations has also been really great as I don’t have to worry about weird bugs of accidentally overwriting some variable. A great example of this is writing a python for loop like for df in dfs: … and then losing the data for the original df that you had before the loop. Yes this can be avoided, but using a Jupyter notebook you will have a ton of variables in global scope.
I feel var should not be optional at all, unless done so via a compiler option and even in this case it should not allow mixing of explicit and implicit declarations within the same method and function.
Yes, local might better indicate the block scoped nature of var declarations. But there are block scoped ref declarations too. I think it is more important that var is used throughout Mojo to represent a “locally declared owned value binding”.
The Mojo team has made a deliberate choice to stay close to Python syntax and semantics unless technical constraints require deviation. From what Chris mentioned, these differences are expected to stay.
Because of this, I’ve decided to avoid Python-style implicit variable declarations entirely in my own code. I now always use explicit var declarations instead. This helps me avoid the scoping and behavior surprises you described earlier.
I’d advise that new Mojo users also adopt explicit var declarations instead of Python-style syntax, at least while learning, for the reasons discussed in your post. In my humble opinion, that’s currently the best we can do.
@Wouks: Your example points to another difference between Mojo and Python: loop variables are always block scoped in Mojo.
Python:
def main():
value = 0
values = [1, 2, 3]
for value in values:
print(value)
print(value) # prints 3, the last value of the loop variable. Everything is function scoped.
main()
Mojo:
def main():
value = 0
values = [1, 2, 3]
for value in values: # not using `var` here means: read-only, but block scoped.
print(value)
print(value) # Surprise!, prints "0".
This is one more reason why I would like an option to require explicit var declarations. This implies that everything is block scoped.
def main():
var value = 0
var values = [1, 2, 3]
for value in values: # not using `var` here means: read-only, but block scoped.
print(value)
print(value) # No surprice, prints "0". Everything is block scoped.
Either Mojo implements Python variable scoping rules, or it defines its own. Having this in between state will lead to subtle bugs and mismatched programmer expectations.
IMHO the compiler should issue an error here - for-loop variable must have a unique name. Perhaps with a compiler option to downgrade that to a warning
Is there any reason that makes mixing implicit and explicit variable declarations inside the same function a good thing? If no, why allowing it at all?
There is no reason for having two ways of declaring variables at all. “There should be one-- and preferably only one --obvious way to do it.” (Zen of Python.) “In Mojo however, we intentionally stay close to Python to make it easier to learn (and many other reasons). The cases we diverge (e.g. requiring static types) are related to the core mission and use-cases we need to support” Chris Lattner.
One of the two have to go and if you follow Chris’ mantra then the “Explicitly-declared variables” must go.
From the mojo documentation:
Variable declarations
Mojo has two ways to declare a variable:
Explicitly-declared variables are created with the var keyword.
var a = 5
var b: Float64 = 3.14
var c: String
Implicitly-declared variables are created the first time the variable is used,
either with an assignment statement or with a type annotation:
a = 5
b: Float64 = 3.14
c: String
I suggested to add for declaration + type inferrence:
a := 5
And restrict the usage of
a = 5
To the assigment of a new value to an existing variable.
I was told in this forum that this it technically impossible or just deviates too far from python. Oh well, whatever it is, it’s definitely closer to python than the current system.
Using := to declare a variable in Mojo would be even more out of sync with Python.
If you write this in Python you will actually get a SyntaxError. (Tested on 3.14.3)
def main() -> None:
x := 5
print("x is:", x)
if __name__ == "__main__":
main()
So using this in my opinion would deviate even more from Python.
That being said I am wondering how scoping rules for the walrus operator should then work. Currently it does work like Python, but I have always felt that this can hurt readability as people can now create variables in the middle of the elif section.
def main() raises -> None:
var x = -1
for x in range(10, 16): # this x is used within the for loop (not in the else section)
print(t"Within for-loop x={x}") # prints 10 to 15
if x > 20:
print(t"Within for loop: x={x} | BREAK") # does not print
break
else: # no break
print("No Break occurred:", x) # prints -1
print("This is x outside of for loop:", x) # prints -1
var y = -1 # assignment to 'y' was never used; assign to '_' instead? warning
var z = -10
if (y := 10) > 10:
print("This is y within if branch:", y) # does not print
elif y > 0 and (z := 100) > 0: # y is now assigned 10 here
print("This is y within elif branch:", y, "This is z:", z) # prints y=10 and z=100
print("This is y outside of if scope:", y) # prints 10
print("This is z:", z) # prints 100
This is the output:
/home/wouks/Documents/MojoProjects/test_new_mojo/main_walrus.mojo:13:13: warning: assignment to 'y' was never used; assign to '_' instead?
var y = -1 # assignment to 'y' was never used; assign to '_' instead? warning
^
Within for-loop x=10
Within for-loop x=11
Within for-loop x=12
Within for-loop x=13
Within for-loop x=14
Within for-loop x=15
No Break occurred: -1
This is x outside of for loop: -1
This is y within elif branch: 10 This is z: 100
This is y outside of if scope: 10
This is z: 100
So the for-loop with the else conditions makes sense to me with the scope rules that Mojo has.
With the walrus operator I would expect the := assigned variable to only be available within the if-elif-else blocks/statement and then no longer outside of that block given the scoping rules. I know this would not agree with how Python does it, but we already have now different rules and having this one act this way seems to clash with the current scoping rules.
I agree that these would be more in sync with python:
b: Float64 = 3.14
c: String
But this one of a := 5 would not be and that is all that I meant with that. In Python this is not an assignment statement, but rather a assignment expression. Which is why a SyntaxError is raised in Python when used like a assignment statement. Obviously they could decide in Mojo to go that route and do it like this instead if they wanted to.
Just my personal opinion, but I actually like the var keyword. I also understand people will have different preferences. I just feel Mojo is already so different from Python in a lot of aspects, that it has become a bit hard indicating that one thing needs to be closer to Python while something else must absolutely change.
I was actually a bit disappointed when they removed the let keyword from Mojo a while back. I thought having a way to declare that a type was immutable was pretty handy and made you not have to think of whether that variable might have a different value elsewhere in the function.
So whatever decision is made by them on this, it would just be a matter of consistency within the Mojo language while keeping the scoping rules. I feel that is a major improvement for readability and to prevent confusion as I indicated earlier with the for-loop scoping difference with Python.
“But this one of a := 5 would not be and that is all that I meant with that.” Oh really?
This a := 5 is used in the context of expressions for declaring + initializing variables in python. It’s known as the walus operator. So this is used in a different context with the same meaning in python. So this use much closer to python than var, var, var…