Struct Fields – Another Place Where var Feels Out of Place

I understand that requiring var inside structs was likely inherited from Swift. However, this makes Mojo and Swift clear outliers among modern languages.

Most other languages do not require any extra keyword for struct/record fields:

  • C++: std::string name;
  • Rust: name: String,
  • Go: Name string
  • Zig: name: []const u8,
  • Python (dataclasses): name: str

In all these cases, the surrounding struct context already makes the intent obvious. Forcing var here adds visual noise without providing additional value.

This appears to go against Chris Lattner’s stated design philosophy for Mojo:

“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.”

I don’t see how requiring var in struct fields serves the core mission. Rust, Go, and Zig show that statically typed, compiled languages can do perfectly well without it.

This fits into the broader pattern I’ve raised before: var is currently overloaded across too many contexts (local variables, function parameters, struct fields, etc.). It seems the var keyword is sometimes defended based on historical habit rather than questioning whether it truly adds value in every context.

While this may feel familiar to Swift developers, it creates unnecessary friction for Python developers — Mojo’s main target audience.

A cleaner approach, consistent with my earlier proposal, would be as simple as:

struct Person:
    name: String
    age: Int

I’m sharing this feedback because I genuinely want Mojo to succeed and offer the best possible experience for Python developers. An outside perspective can sometimes be helpful.

Happy to hear the team’s thoughts on this.

I think this makes sense. If fields can’t be anything but var, then it’s redundant.

On the other hand, I guess it serves as a reminder to anyone reading the code that that’s what they are.

It doesn’t look like an oversight though. Mojo structs | Mojo states:

Syntactically, the biggest difference compared to a Python class is that all fields in a struct must be explicitly declared with var.

So I presume there’s a reason.

Interesting topic.

I don’t think habit is the reason here. Maybe folks advocate for consistency and familiarity but those are about ease of use rather than unexamined custom. I think the primary reason for var (in general) is that it supports correctness that the compiler can enforce. Especially in kernel programming (which can include many lines of complex logic) it is possible to intend to declare a new variable and instead accidentally reassign to an existing variable.

def main():
    x = Int8(0)
    # ... lots of code ...
    x = UInt.MAX   # you think you're declaring a new variable
                   # you're actually invoking Int8's implicit constructor
    print(x)       # prints -1, not UInt.MAX

using var = x here raises a compiler error.

As your thread is about struct fields in particular I think @christoph_schlumpf put it well:
In all usages var has the same meaning: "a locally declared owned value binding“.

Good note Daren. You can also define comptime values as members of a struct or trait declaration:

struct Circle[radius: Float64]:
    comptime pi = 3.14159265359
    comptime circumference = 2 * Self.pi * Self.radius

I agree with @KilianKlaiber the usage of var feels arbitrary and inconsistent in places. The fact that it could be “comptime” doesn’t change that.

if the goal is to stay close to Python, it should be omitted in struct context. Right now Mojo wants to have the cake and eat it too

I think the discussion is being framed too much in terms of implementation details and not enough in terms of what the programmer actually observes.

Consider the following declarations:

name: String
var name: String

Both introduce a local binding named name of type String. The binding exists in both cases. The additional meaning contributed by var is therefore not “declare a variable” in the general sense, because a variable has already been declared without it. The only difference is that var creates a block scoped variable.

From the programmer’s perspective, var adds the notion of a block-scoped local variable binding.

Now consider a function definition:

def consume(name: String):
    ...

The parameter name is already a variable. It has a name, a type, a scope, and a binding convention.

Now compare that with:

def consume(var name: String):
    ...

Here, the observable effect of var is no longer “create a block-scoped local variable”. The variable already exists. Instead, var changes the ownership semantics of the argument and causes ownership to be transferred into the function.

So the meaning of var appears to change depending on context:

  • In local declarations, it introduces a particular kind of scope.
  • In function arguments, it expresses ownership transfer.

These are different concepts.

I understand that there may be a deeper compiler-level explanation that unifies both cases. However, language design is ultimately about what the programmer sees and reasons about.

The observable semantics presented to the programmer are different enough that the keyword no longer communicates one clear concept.

This becomes even more apparent when considering alternative spellings. If the purpose in parameter position is ownership transfer, then:

def consume(own name: String):
    ...

communicates that intent immediately.

The word own naturally suggests ownership. The word var naturally suggests a variable. Nothing about the word var inherently suggests ownership transfer.

For that reason, I find it difficult to argue that var carries a single, obvious meaning across all contexts. While there may be an internal conceptual model that unifies these uses, the semantics experienced by the programmer are sufficiently different that the keyword does not communicate one clear idea on its own.

I think in the context of Mojo one would not call this a “variable” but a “value binding”. In this case it is a “read-only reference”.

Declaring a “value binding” in mojo can be done with a variable (var) or a reference (read, mut or ref) convention. Variables always own the value while references never own the value.

comptime can be used to declare a compile-time value binding.

But I agree that in the context of struct instance variable declarations var is somehow redundant and could be omitted as the default value binding convention. I personally still would keep it as it is explicit in its meaning.

BTW: In Mojo name is even not called a "parameter“ but an “argument”. But this is a controversial topic on its own already :wink:: Feedback request: Should Mojo adopt "specialization" for what we currently call "parameters"?

In function arguments var just expresses ownership. This can be in the form of ownership transfer (a “move”) or creating a new value (a “copy”). The same is true for local declarations inside function bodies:

def consume(var value: Int):
    print(value)

def main():
    var a = 1 # a new value
    var b = a # a copy
    var c = b^ # a move (ownership transfer)

    consume(c) # a copy
    consume(c^) # a move (ownership transfer)
    print(a)

But the compiler applies it‘s own magic to avoid copying and to work with register passable types.

Maybe eventually mojo would have something like:

struct Person:
    var height: String
    mut name: String
    read age: Int

So redundantly tagging at the start of the value at least allows for adding additional key words later. Currently you need to do a: var some_field: Pointer[origin, mut] style field setup for reference + mutability defining. It would be nice to have short hand at some point.

“Declaring a “value binding” in mojo can be done with a variable (var) or a reference (read, mut or ref) convention.”

Thank you Christoph, I appreciate you engaging with this.

Yes, in function arguments var primarily expresses ownership — that part is relatively clear. But var is not an obvious name for taking ownership.

My bigger concern is the overall consistency across all contexts. Even if we can define a single internal meaning (“owned value binding”), the experienced meaning for the programmer still changes depending on where var is used:

  • In function arguments → mainly “take ownership”
  • In local declarations → “block scope”
  • In struct fields → “this is a field”

This variation in perceived semantics is what makes it feel inconsistent and not “one obvious way” (Zen of Python).

That’s why I believe using own more explicitly (and removing mandatory var) would make the language clearer and more approachable, especially for Python developers.

Great idea! This aligns perfectly with what I’ve been suggesting.

We should aim for consistent declaration syntax across the entire language:

  • Struct fields
  • Local variables
  • Function / method arguments

Your proposal is very close to mine. I would only go one step further and replace var with own:

struct Person:
    own height: String
    mut name: String
    read age: Int

If you do not declare the binding, then it will be inferred by the compiler. Sou you can skip the binding if you want and would have the original python syntax.

own is much clearer and more intuitive than var, especially for Python developers. It directly communicates the ownership semantics, while var has become heavily overloaded and doesn’t carry a single obvious meaning.

This way we’d have one clean, consistent set of binding keywords (own / read / mut) used everywhere in the language.

What do you think?

@clattner and other Mojo guys, please remove this redundant/noisy ‘var’ prefix when declaring struct fields. It doesn’t make sense really, and honestly it reminds me terrible Rust design where everything is private be default and to make a struct public one has to write ‘pub’ on every single line.

Welcome @bomzj ,

For some context from Discord :

struct fields might be refined at some point after Mojo 1.0 is released.

Adding ‘ref’ doesn’t make any sense to have ‘var’ at all .

One more thing If Mojo really strives to be alingned with Python style then ditch this ‘var’. ‘ref’ or anything else can be added later if needed.

True, IF var is the sensible implicit default value binding convention for struct fields. var has quite a different semantics than class fields in Python. If in the future the implicit default should more closely reflect Python semantics (or any other convention) it would be a breaking change. With explicit var conventions it is still possible to decide on an implicit default convention later on without breaking any code.

In Python it is possible to assign the same value to different class fields:

from dataclasses import dataclass

@dataclass
class MyClass:
     x: list[int]
     y: list[int]

value = [1, 2, 3]
data = MyClass(value, value)
value.append(4)
print(data.x, data.y) # prints [1, 2, 3, 4] [1, 2, 3, 4]

This is not possible with Mojo struct field var conventions. You must move and/or copy the value into the struct fields. They can’t have the same value assigned.

And then there are developers that like to have explicit syntax because one has not to remember the implicit default convention and because it is simpler to spot all declarations and reason on them.

Honestly ‘var’ is terrible naming in that case even for fans of explicitness, since ‘var’ means variable rather than constant which Mojo declares. Explicitness should be meaningful rather than noise which it is as of now. Haskell is a very beautiful example of removing all noise where all variable is immutable which is fine, and it would be nice for Mojo to have good signal/noise syntax not repeating old language bad designs.

This has nothing to do with the var keyword itself. It’s simply a consequence of Python’s reference semantics for mutable objects. In your example, both fields receive a mutable reference to the same list object, so mutating that list is visible through both fields.

Yes, Python has reference semantics and Mojo has value semantics. In Mojo var explicitly introduces an owning value binding. Inside functions one can use ref for references. But there must always be one implicitly or explicitly declared variable (var) that owns the value. That’s a direct consequence of Mojos ownership model.

var is a variable in Mojo and not a constant. Variables in Mojo have strict ownership guarantees.

“In Mojo var explicitly introduces an owning value binding”

I don’t think “owning value binding” accurately describes what var means in Mojo. For example, name: String = "Hello" already takes ownership of the value by default. Adding var does not change the ownership semantics, so ownership cannot be the defining characteristic of var.

My concern is that var is overloaded: it is used for struct fields, local variables, and function parameters, while its meaning depends on context. The keyword var does not communicate its semantic property. Last but not least, var deviates without any good reason from the standard python syntax.

The keyword var should be deleted and the obvious keyword own should be introduced for ownership binding.