Rust: var keyword for mutable locals only

Created on 20 Jun 2012  ·  35Comments  ·  Source: rust-lang/rust

Issue #1273 proposed let mut for mutable locals and mut for mutable fields. let mut is more verbose than a single keyword and also breaks column alignment. People rightly didn't like the idea of var for mutable field declarations. But I think nobody suggested the idea of using var for mutable local declarations and mut for mutable field declarations:

let x = 7; // declaration of immutable variable x
var y = 7; // declaration of mutable variable y
type point = { mut x: int, mut y: int }; // declaration of mutable record type

Dave

I-nominated

All 35 comments

I have to confess, I at first thought that we should use the same keyword for mutable vars and mutable fields, but var does read kinda' well (though I don't dislike let mut)

I think it's a feature that declaring a mutable variable is ever-so-slightly more cumbersome than an immutable one. I can see programmers using just "var" to be "consistent" or because "var" would be more powerful (don't have to change it if you decide to mutate later). Then you end up with less readable code overall.

Not suggesting that Rust should be a serious bondage & discipline language, but a gentle nudge in the right direction is morally justifiable, I think (i.e. the rule would be "for safe code, the safer constructs should be less noisy than the more powerful but easier-to-mess-up constructs").

On one hand I like this, since it would remove the visual ambiguity from this form:

let mut x = 4,
    y = 8;  // is y mutable or not?

But I'm inclined to side with ssylvan. Declaring mutables doesn't exactly need to be _ugly_, but I think it makes sense if they're ever-so-slightly less convenient to create than immutables, if only by a single keystroke. The activating keyword also needs to be distinctive, and (IMO) var is too widely used as a generic variable declaration keyword to specifically convey mutability in a language that prides itself on immutability-by-default. And think of the poor C# programmers, for whom var is our let!

I still like the idea of replacing let mut with a single keyword, so as to address the code snippet above, but is there any reason for introducing a new keyword when mut itself would suffice, as per the original proposal in #1273?

@pcwalton pointed out a problem with just using mut: there is an ambiguity with record literals and block expressions that requires arbitrary lookahead to resolve.

{ mut x: int ...

Record literal, or block with local variable?

I could see a lot of programmers just learning that var is how you declare variables in Rust, and not using let at all. After all, var is how you declare local variables in languages like JavaScript. I'm inclined to think this is a good thing about this proposal.

I overheard someone point out yesterday that now let and var would have the same length, which would be nice for alignment.

I'm somewhat indifferent, but I somewhat prefer var, because it's shorter. Making mutability annoying isn't a desirable goal as I see it. (Indeed, I tend to think the role of a programming language should not be to make anything annoying—just _clear_, which isn't the same thing.)

While I'm still wary of using let and var together (just like Javascript, but 100% different), it would be much less of an issue if there were a lint pass to detect variables that are declared as mutable but never actually mutated.

There is a plan to make mutability a part of the type. Does that affect locals and make this irrelevant?

How about:

val x = ... // immutable (val-ue)
var y = ... // mutable (var-iable)

Like in Scala.

I think @brson is right and this issue vanishes once we move mut into a type, i.e. you get let x = mut 10;

Closing this issue for now; reopen if you think I'm wrong!

I am not sure about this. I like the idea of moving mut into the type, but I don't know that it's a "done deal"---there may be lingering weirdness in there. In any case, I never considered that one might write let x = mut 5, I always assumed you'd write let mut x = 5 just as today; the "mutability-ness" of the type of a variable would come from the way it was declared, not the value being assigned to it.

To do otherwise would seem to imply that if you have an array x of type [mut int] and you write let y = x[0] then y is mutable? Or something? That seems undesirable.

@Dretch I don't love val/var because they are not distinct enough, though the Scala precedent is nice.

I share @eholk's concern about people learning to use var by default. The way it works now I tend to declare everything as immutable, then the compiler reminds me it should be mutable, then I type mut. This is arguably good behavior that you would be less inclined to with a var/let split - typing var and let are equally difficult but you can't even type let mut without typing let.

But I don't h ave a preference and I do appreciate when I can compose functions entirely of statements beginning with three-character keywords.

@nikomatsakis In particular, it makes sense to me that the rules about single-assignment should come from the mutability of the declaration rather than the type. Subtly changing the assignment rules based on type smells funny to me.

I'm inclined to agree with @pcwalton that we shouldn't penalize programmers from using a mutable binding if that's what they want. As for the concern about people unnecessarily using var, we could add an optional warning that complains if a var binding is singly-assigned. But I also think that we can set precedents for good style in rustc and the standard library.

Dave

Is it really that terrible if programmers declare all their variables mutable? It seems like it's not the end of the world if we have a set of Rust programmers that just think var is how you declare variables, and another set that understands to use let most of the time and var when needed. As the first Rust programmers, we can set the precedent in favor of using let and var correctly.

IMHO good syntax design is not just about making "everything" you might ever want to do convenient, it's about gently nudging people on to the "smooth path" of the language's semantics and design goals.

For example, you probably wouldn't add special syntactic support for linked lists in Rust (a la Haskell), because one of Rust's foundational principles is to be efficient, and pervasive use of linked lists will work contrary to that principle. For the same reason sharing mutable data between threads probably shouldn't be too convenient (since safe concurrency is another principle), nor should it be super convenient to cast an arbitrary int to a pointer (since memory safety is a big principle).

Not to say that it should be impossible to do any of these things, mind, just proportionately inconvenient so that it's clear from the syntax which is the idiomatic way to write Rust.

Mutable (local) variables aren't nearly as bad as any of these, but if Rust is indeed favouring immutable data for correctness and maintenance reasons (something I personally agree with), then the syntax should ideally give a gentle nudge in that direction. Even a single extra character or an extra modifier sigil or whatever would be enough to make it clear that "let" is less complicated than "let mut" or "let!" or whatever, and therefore must be the preferred default you should try to go for when you don't actually need the variable to be mutable.

@ssylvan Oh, I understand that point, it's just a question of degree, and the balance of trade-offs. We already promote immutability of data structures, and IMO immutable locals are less important to promote than immutable fields. (Especially since, IINM, we don't allow mutable locals to escape in heap closures.) And the loss of the ability to refactor between let and var without changing column count outweighs the benefit of promoting immutable locals. Hard to quantify, so I guess it's just my feeling.

Dave

Well in that case at least "let foo = mut bar" or "let foo := bar" as opposed to "let mut foo = bar" would make the first token line up. Presumably the variable name will be of variable length so it's not so important to avoid extra modifiers on the rest of the statement.

Oh hey, I'm kinda partial to the := idea.

Dave

On second thought, Pascal is permanently uncool. I take it back. :)

Dave

Also, let foo := bar prevents something like this:

let mut foo;
foo = bar;

I've found that pattern useful at times, although it seems like there's probably always another way to write the same pattern.

@eholk I don't think it prevents that. But I still think it'll look too weird to programmers from almost any mainstream language.

Dave

Regarding :=, Go uses it to indicate type-inferential assignment (though it's not quite a mainstream language yet). But I can foresee difficulty distinguishing between the two forms at a glance:

let foo = "hello";
let foo := "hell";

brson's argument for the current syntax (i.e. that the mutable declaration first requires the immutable declaration) is convincing. It's totally great if programming languages are opinionated, just as long as they aren't jerks about it. :)

Not interested in = vs. :=. Mostly opposed to val, var and variations thereof; it's simply not _obvious_ that it controls mutability. I mean, I won't quit in disgust if we adopt one of them, but I think "needing to explain the mnemonic" is a bad sign. I'm more-ok with:

  • Letting the type dictate it.
  • Letting mut alone work as a local-declarator and requiring the parser to delay committing to record-syntax vs. local-declaration one extra token; it's still LL(1) is just adds an extra intermediate grammar-state, no extra backtracking (both ways forward are valid).
  • Leaving it as let mut.

The main reason to avoid "accidental" mutable locals is that we introduced environment capture, so they turn into a form of action-at-a-distance, as well as hazards for a variety of analyses like borrowing.

(All lets were initially mutable, but we also had no environment capture, only bind. Now we have no bind, only env capture. Tomayto, tomahto.)

I believe mutables cannot be implicitly captured now.

@graydon is correct that there were two original motivations. However only one is still relevant. The two motivations were

  • implicit "by-copy" capture of mutable variables in an fn@
  • understanding what data is mutable and what is not for borrowck

It turns out that the latter is no longer relevant. The use of mutable/immutable variables was too crude in practice so borrowck has the idea of borrowing a variable "temporarily"---a mutable variable can be borrowed with an immutable ptr so long as the variable is not modified while the pointer is in scope.

We could perhaps just remove the idea of mutable/immutable locals and go back to the old rule---everything is mutable. We could then issue warnings when a variable that is implicitly copied into a closure is modified after the closure is created.

There's a third motivation: immutable variables are easier to reason about. If everything is mutable you have to scan around the whole function to see what values a variable might have during its life time. Every variable potentially has complicated data flow (esp. with loops, branches, mutable function parameters, etc.) and it's hard to see what's going on without carefully analyzing every statement. If you have only one or two mutables in a function it sort of acts to "flag" them so that you're more careful when reading code involving hem.

@Dretch I also like the Scala style, with "val" and "var" key words.

I like how the current syntax causes mutables to stick out like a sore thumb; it makes scanning the code easier. val and var seem too visually similar in that regard. Which is not to say that mutables absolutely _must_ stick out, but keeping keywords visually distinct is an important facet of usability.

I understand that mutable fields are going to be removed from rust. Does that mean that mut could then be used instead of let mut because the record/variable-in-block ambiguity would disappear?

Also I believe structural records are going, which removes the ambiguity even if mutable fields remain.

@Dretch it's true that mutable fields are on their way out, and structural records are already gone.

I'm mostly indifferent to the issue, although I would like to point out that it _might_ make sense for mut to be a declaration keyword in its own right (as Dretch proposes), in the case of freezing/thawing. Compare today:

let foo = 1;  // immutable
/* 10,000 lines of code here */
let mut foo = foo;  // we're making foo mutable, totally understandable
/* 10,000 lines of code here */
let foo = foo;  // potential wtf

With the proposal:

let foo = 1;  // immutable
/* 10,000 lines of code here */
mut foo = foo;  // a mutable foo, no problems here
/* 10,000 lines of code here */
let foo = foo;  // slightly less of a potential for wtf, since we officially have two declaration forms

Though I also feel like this would make the Rust-ism "absence of mut implies immutability" less consistent, because we'd still be writing let foo = 1; rather than just foo = 1; (the latter form is obviously undesirable for declaration).

Kotlin also uses val and var.

I don't think we're going to make this change, but I'll nominate for milestone 1, well-defined, so we can settle it.

consensus is to not do this, as it's incompatible with moving mut to pattern bindings. closing.

Was this page helpful?
0 / 5 - 0 ratings