Suggestion: "mut", like "let" keyword

#16

Sorry, I can’t understand this part yet. What exactly we access if braces here are just normal block? Right now for me it’s the same as write { let x = 5; println!("{}", x); }.some_field - what is the difference?

#17

Nothing. It’s literally just a block. The value of the block is the value of the final expression. So if the block only has one expression, then that is the value. Therefore the value of {x} is just the value of x itself.

2 Likes
#18

The difference between x.value = and {x}.value = is that x is a place expression whereas {x} is a value expression. See https://doc.rust-lang.org/1.30.0/reference/expressions.html#place-expressions-and-value-expressions for a detailed explanation.

#19

thanks, now I get it

#20
let mut k;
loop {
    k = fetch_data(); // should this get a `mut` prefix?
    if good(k) { break };
}
#21

I’d rather use something like “mutable accessor” for mutating fields:

let x = 6;
thing~value = x;

And for calling functions which takes &mut self:

thing
    ~mutate()
    ~mutate_again()
    .move_it();

If you interested, more use cases and syntax variations could be found in this thread.

#22

This is not why dyn was introduced. It was introduced to remove ambiguity and make trait implementations easier to read/write. From the RFC:

The current syntax is often ambiguous and confusing

Because it makes traits and trait objects appear indistinguishable. Some specific examples of this:

  • This author has seen multiple people write impl SomeTrait for AnotherTrait when they wanted impl<T> SomeTrait for T where T: AnotherTrait .
  • impl MyTrait {} is valid syntax, which can easily be mistaken for adding default impls of methods or adding extension methods or some other useful operation on the trait itself. In reality, it adds inherent methods to the trait object.
  • Function types and function traits only differ in the capitalization of one letter. This leads to function pointers &fn ... and function trait objects &Fn ... differing only in one letter, making it very easy to mistake one for the other.

In fact, the RFC also points out that introducing dyn as a ‘warning’ or syntax salt is not sufficient motivation:

The current syntax is often ambiguous and confusing, even to veterans, and favors a feature that is not more frequently used than its alternatives, is sometimes slower, and often cannot be used at all when its alternatives can. By itself, that’s not enough to make a breaking change to syntax that’s already been stabilized.

3 Likes
#23

Yes, it is strange. The quirk is explained here: https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/

Ignoring oddity of that syntax for a second, the bigger issue is that there are two kinds of mutability/immutability in Rust:

  1. & and &mut which are critical for Rust’s concept of safety, and have to be strongly enforced. Here mutability is about the referenced value.

  2. mut in bindings, which is not needed for safety, and it’s more like a lint or a comment for the programmer. Here mutability is about the binding (variable) not the value, so it doesn’t apply when accessing the value indirectly.

The second kind is not really needed by the language. It doesn’t directly affect safety, correctness or code generation. If it was removed from the language (i.e. let was made same as let mut, and mut became meaningless no-op) nothing would break!

So really all let/let mut is for is a “note to self” for the programmer to state the intention clearer. It’s a shorter version of // this is the one and only assignment to this variable vs // I'm going to be mutating this variable later.

…so any proposal that makes it “easier” to change values of let variables just makes this let/let mut distinction even weaker and more useless.

#24

Looking at title, I expected it suggests to make mut x = 6; mean let mut x = 6;, i.e. ability to omit let if there is already mut.

#25

That was my initial interpretation as well, but OP’s later comments led me to revise my interpretation. I suspect that if Rust had used a sigil such as § for the mutability attribute, rather than the alphabetic sequence mut, these confusions between verbs (let) and attributes (§, for mut) would never arise.

#26

I strongly disagree. Multiple times I’ve been experiencing warnings like “x doesn’t need to be mutable” - and every time it was signal about huge (logical) mistake in my code. It’s not just lint, it’s much more. Also, “immutable by default” is not just lint, this approach can help to create code with different architecture, different basis - it helps to write more safe code.

4 Likes
#27

Eh. When I see stuff like this I can’t help but feel that people are misplacing their enjoyment of immutability in general. Here is how I honestly feel about it:

  • Not being able to get a &mut U from a &T is one of the core principles of Rust.
  • Not being able to get a &mut U from an immutable T is an annoyance (when writing code) that, in my experience, is almost always correctly solved by adding a mut. But, it has two great payoffs:

First, it makes the unused_mut lint one of the top most useful bug-catching lints, right up there with unused bindings. Oftentimes I might know for instance that I want to collect some data from an iterator into a vector, and then sort it. Thanks to mut being required I now have it trained in me to write let mut vec: Vec<_> = in anticipation of the sort:

let mut vec = {
    things.iter()
        .some_long_and(complicated)
        .pipeline_of(iterator, adapters)
        .collect::<Vec<_>>()
};

but then after all of that, I often forget to finish what I started and actually call sort!

Second, reading the code after it is written, anybody who sees mut can expect that something will happen to the value after its initial assignment. (this is, of course, also thanks to the unused_mut lint)


I can contrive an example where adding mut is not the solution:

  fn ancient_function() {
      let items = {
          things.iter()
              .some_long_and(complicated)
              .pipeline_of(iterator, adapters)
              .collect::<Vec<_>>()
      };

+     items.sort();  // sort the output that gets printed
      print_report(&items);

      save_file(items);  // (note: expects items to be in original order)
  }

but to make a mistake like that seems… rather careless, enough so that I doubt that the author would notice it even after the minor speedbump presented by the "missing mut" error.

5 Likes
#28

I don’t understand why it is more of an annoyance than the lack of a &T -> &mut U conversion. Both are erroneous; disallowing this is not any more “annoying” than e.g. disallowing calls to nonexistent functions.

#29

Having a &T means that other code can observe changes to the value.

Having a T means that other code can’t. I mean, it’s a stronger condition than even having a &mut T!

This point of view is simply colored by the present state of affairs. The compiler has many valid reasons to disallow calls to nonexistent functions (for starters, it can’t typecheck them!). Requiring a mut keyword on a binding in order to mutate it is a completely arbitrary decision.

#30

Being able to get a &mut U from an immutable T would not affect soundness. Being able to get a &mut U from a &T would. That is, &T vs &mut T is required for memory safety; mut on bindings is not.

1 Like
#31

It might be arbitrary from the perspective of soundness, but it certainly isn’t completely arbitrary in the sense of lacking a strong rationale for the behavior.

6 Likes
#32

I definitely don’t disagree there. :slight_smile:

The requirement encourages better code style by discouraging mutability, indicating the fact that we have learned from the past mistakes of other languages. It’s hard to find a style guide for e.g. C++ or JS that doesn’t say to use const wherever possible. This alone was more than enough to justify its inclusion.

I guess my feeling is partly that, now that we have been writing in rust for a while, we’ve had the chance to learn even more; and what I feel that I’ve learned is that the &/&mut distinction alone may very well have been enough to wipe away all of those classes of mistakes that beg for immutability by default in other languages.

2 Likes
#33

I now see where you are coming from, but I still disagree that we should just trust programmers again, since “we’ve been writing Rust for a while”. People are still terrible at writing perfect (as in “correct”) code, and throwing away some of the clutches that we already have seems like a clear regression.

#34

I haven’t learned the same thing :wink:

To me, let mut provides a good “heads up” marker that I need to go into an imperative mode of thinking; if it isn’t there, I stay in a mode in which I know that the probability of mutation is lower (interior, yada…).

I think having let mut also encourages a more functional writing style that bends towards using iterators; in turn, this reduces the distance to parallelization.

All in all I think let mut was and remains the better decision and I think we should still enforce it.

6 Likes
closed #35

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.