Show warning only when let variable is modified

Most of my points were already made by other people, but I’ll allow myself to reiterate and hopefully summarize for the sake of posterity, especially since more examples of “why having mut is good” were requested.

Personally, I’m not sure why in the year 2018 after decades of existence of functional languages it’s still not accepted as common sense that immutable-by-default bindings have their very pronounced benefits, no matter how “the mut keyword is redundant because compiler always knows if a binding needs to be mutated” or “generated code will be the same anyway” or even “but mut is 3 more symbols to type and learn”.

In my very honest opinion, a programming language, especially one that “helps you write more reliable software”, exists not for the sake of the compiler, and not even for the sake of developer’s typing or even learning convenience, and especially not for getting a juicy share of the Python userbase, so that Rust users number looks more impressive.

It exists so that a developer that finds themselves in actual need to write something more reliable than a Python script can precisely convene their intent to the computer and to other collaborating developers. And the so-called “redundancy” in a language is actually what helps readability and nailing the intent down, including preserving the intent across refactoring and further development.

let a = ...;
let b = ...;
let mut c = ...;

Immediate readability benefit. Now one immediately knows which binding one needs to keep a close eye on. Doesn’t seem worth it? Ok. Let’s add a loop.

let a = ...;
let b = ...;
let mut c = ...;

loop {
	...
}

Now, looking at this code you can immediately know that loop’s state space is merely {all possible values of c}, instead of the much larger {all possible values of a}x{all possible values of b}x{all possible values of c}.

In order to analyze the behavior of the loop only changes to c need to mentally simulated across more than one iteration.

Values of a and b are immediately known at the beginning of every iteration. You know, that there wouldn’t be subtle shenanigans with changes to a and b accumulating in a way that could affect the loop ending condition.

In other words, immutability keeps down the execution course variants you need to consider and keeps the chaos under control.

I disagree with the often voiced argument, that mutability-caused bugs limit themselves to aliasing bugs. Let’s observe how bugs could be easily introduced, if all bindings are implicitly mutable, with the following dumbed-down example.

let a = ...;
let b = ...;
let c = ...;

loop {
	c.do_something();

	// do some other stuff...
 
	if b.check_c_stuff(c) {
		break;
	}

	// still do stuff...
}

The intent was only for b to observe c, doing some complicated calculation based on its own data and the data in c. The data in b is not supposed to change, so naturally it is constructed outside the loop for clarity and performance reasons.

It seems fine and dandy, but then one day someone changes fn check_c_stuff(&self, ...) to fn check_c_stuff(&mut self, ...), because calculating stuff seemed somehow faster while mutating in place.

And everything still compiles. But the loop may now behave in a complicated way, and may even not end at all, due to the fully unintended even though perfectly unaliased mutation that allowed changes to accumulate across iterations and thus corrupt loop end condition logic.

The mut keyword is “inconvenient” for some, because it forces the developer to reconfirm their intent. And I think this is a good thing™, if you do actually want to write reliable software.

Did I actually want changes in b to accumulate? Or do I now want to create a fresh instance of b for every iteration inside the loop? The language asks me that, and when I don’t want my language to ask me that, then I write in Python, not Rust. Which doesn’t mean that Rust is a bad language. (Nor is Python, for the record).

Rust is not Python, and it’s a good thing, because I don’t believe in “one language that rules it all”. I feel the need to point out, that making Rust more “familiar to developers coming from other languages” just to get some more users on board may also take from Rust’s arguably strong points, make it unsuitable for some needs and niches, and make it not that different from hundreds of languages out there. All of which will make it less attractive to learn and write in, in the first place, and thus will ultimately get less users on board in the longer term.

The arguments I also disagree with:

  1. let immutability is a lie, because there is internal mutability.

There are things without internal mutability too, and a lot thereof. A feature doesn’t have to be 100% perfect and watertight to be helpful. If I wanted really watertight, I would have opted for a language with formal verification annotations. Meanwhile, in the absense of formal verification in Rust, let immutability strikes a good balance between easiness of writing code and facilitating writing correct and readable code.

  1. let immutability is a lie, because you can mutate the thing by simply rebinding.

It still provides useful information about how the binding is used in the meanwhile.

  1. let immutability is useless because you can miss where a variable is rebinded or shadowed in between.

The simplest implementation of “Goto definition” in any editor would bring you to the last redefinition point, so you won’t miss anything in between.

  1. let immutability is bothersome because you always have to scroll to the binding and add that “mut” if you forgot it.

Intellij Idea IDE highlights the error at the site of mutable use and allows to fix the binding with the “Quick fix” shortcut without having to scroll anywhere, as well as all such errors in the file or your whole project.

It is my firm opinion that providing users with a solid go-to IDE for Rust and/or rustfix tool should be the way to proceed with ergonomic issues like this one, and should be top priority for Rust anyway, if larger user participation is desired.

  1. let immutability can be made a lint and then people who want the additional nudges from the compiler, will have them, and everyone else can be left in peace.

I’m actually split on this one, but, for example, there is an argument for rustfmt to be less configurable, so that there’s sort of “one Rust style that rules them all”, for the perceived readability benefits for everyone.

Disregarding my personal opinion on the matter, a similar argument could be made that making “mut” keyword optional would detract from code style uniformity. After all, one may not always correctly assess how many people will be reading their code and how much they’ll miss the "mut"s for understanding and preserving original intent, not unlike how unfamiliar formatting style can damage readability too.

One may also argue that in order to write reliable software you need to use reliable components, and avoiding sloppy no-mut style everywhere may provide for just a bit of additional edge.

6 Likes