rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.06k stars 12.69k forks source link

var keyword for mutable locals only #2643

Closed dherman closed 11 years ago

dherman commented 12 years ago

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

nikomatsakis commented 12 years ago

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)

ssylvan commented 12 years ago

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").

bstrie commented 12 years ago

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?

nikomatsakis commented 12 years ago

@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?

eholk commented 12 years ago

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.

pcwalton commented 12 years ago

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.)

bstrie commented 12 years ago

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.

brson commented 12 years ago

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

Dretch commented 12 years ago

How about:

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

Like in Scala.

graydon commented 12 years ago

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!

nikomatsakis commented 12 years ago

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.

brson commented 12 years ago

@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.

dherman commented 12 years ago

@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

eholk commented 12 years ago

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.

ssylvan commented 12 years ago

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.

dherman commented 12 years ago

@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

ssylvan commented 12 years ago

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.

dherman commented 12 years ago

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

Dave

dherman commented 12 years ago

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

Dave

eholk commented 12 years ago

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.

dherman commented 12 years ago

@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

bstrie commented 12 years ago

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. :)

graydon commented 12 years ago

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:

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.)

brson commented 12 years ago

I believe mutables cannot be implicitly captured now.

nikomatsakis commented 12 years ago

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

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.

ssylvan commented 12 years ago

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.

lilac commented 12 years ago

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

bstrie commented 12 years ago

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.

Dretch commented 11 years ago

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?

Dretch commented 11 years ago

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

bstrie commented 11 years ago

@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
bstrie commented 11 years ago

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).

chocolateboy commented 11 years ago

Kotlin also uses val and var.

catamorphism commented 11 years ago

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.

graydon commented 11 years ago

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

Jeklah commented 6 months ago

I've just come across this bit of code...

var out: VertexOutput;
let x = f32(1 - i32(in_vertex_index)) * 0.5;
let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5;  

and this is the first time I've come across var in rust. From reading this thread, var is the same as let mut..

So the above code could be re-written as:

let mut out: VertexOutput;

, correct? Thanks all just looking for clarification.

bstrie commented 6 months ago

@Jeklah I'm curious, where did you come across this code? Any Rust code using var must be so old that it would be considered positively prehistoric by this point. You are correct that let mut is the modern equivalent of declaring a mutable variable, although if the code that you're reading is that old then I suspect this will only be the first of many strange things that you will encounter. :)

Jeklah commented 6 months ago

@Jeklah I'm curious, where did you come across this code? Any Rust code using var must be so old that it would be considered positively prehistoric by this point. You are correct that let mut is the modern equivalent of declaring a mutable variable, although if the code that you're reading is that old then I suspect this will only be the first of many strange things that you will encounter. :)

It was in a wgpu tutorial.

I was thinking I think id prefer to use let mut instead of var just for uniformity and less of people asking "what's var for when we have let mut?"

Makes more sense var is an old thing. Thanks for the heads up.

Pauan commented 6 months ago

It was in a wgpu tutorial.

The code you posted isn't Rust code at all, it's wgsl code, which is a completely different language with different syntax and behavior.

Jeklah commented 6 months ago

It was in a wgpu tutorial.

The code you posted isn't Rust code at all, it's wgsl code, which is a completely different language with different syntax and behavior.

Interesting as I'm running it with cargo.

Pauan commented 6 months ago

Interesting as I'm running it with cargo.

I assume you're using the include_wgsl! macro which imports a .wgsl file.

Even though include_wgsl! is a Rust macro, and it's compiled with cargo, the wgsl code is still not Rust.

Rust macros are able to include non-Rust code, such as typed-html including HTML code, or include-sql including SQL code.

Jeklah commented 6 months ago

Interesting as I'm running it with cargo.

I assume you're using the include_wgsl! macro which imports a .wgsl file.

Even though include_wgsl! is a Rust macro, and it's compiled with cargo, the wgsl code is still not Rust.

Rust macros are able to include non-Rust code, such as typed-html including HTML code, or include-sql including SQL code.

That is correct. I'm following a tutorial on using rust with wgsl.