Why can't I increment a variable like this?

In Rust assignments returns (), so you can't use the value of an increment expression inside another expression. This is a good thing, because assignments in expressions can cause bugs, mostly due to ambiguities in evaluation order.

Also, you should prefer iterators over manually incrementing indexes.

So a special abbreviation syntax is not warranted. On the contrary, it is a good thing that you have to type the slightly longer x += 1.

16 Likes

For what it's worth, they had this operator in Swift and removed it a couple of years ago because of very similar reasons as the ones stated in this thread.

Reference: https://github.com/apple/swift-evolution/blob/master/proposals/0004-remove-pre-post-inc-decrement.md

13 Likes

Well the simple i++ or ++i might look innocent. But it often gets complex when you use it as part of a bigger expression.

8 Likes

I feel like it could make sense to allow i++ or ++i, but have it return (). So it's only a short form of an assignment, and cannot be used in error prone ways like in the examples given by the other posters.
It would just be a nice abbreviation, and not cause more errors.

1 Like

At that point it’s an abbreviation of a single character, but one more bit of syntax that has to be maintained in the compiler forever, and learned by readers of Rust code.

19 Likes

Freedom to shoot yourself in the foot is not a rust marketing point :wink:

28 Likes

Can't agree with this strongly enough. Practically every time I've seen non-formatting-related warnings from the Rust compiler it has pointed to a genuine bug in my code.

10 Likes

Also, Rust has had iterator-based for loops from the beginning, so it doesn't need increment operators as much as C does.

8 Likes

At least when Rust hits schools, tests won't be full of questions like:

What is the result of this statement? "x+++++x"

...like it was in our Java test.

17 Likes

++ and -- were originally put into C to map to the post-increment and pre-decrement addressing modes in the PDP-11. That is because C was originally designed as a thin wrapper over the PDP assembly. These addressing modes are used for a lot of stack-pointer movements (that's why you don't get pre-increment or post-decrement).

C++ originally compiled down to C, so it picked them up from there.

Frankly speaking, although they allow you to write dense code, they tend to become cryptic and hard to read, and you have to constantly keep in mind what value you're using.

There are multiple places where C syntax can probably be made denser but is not, so the argument for shorter/denser code with ++ and -- is dubious. In a modern language, the ambiguities (undefined behavior) and subtle bugs introduced probably make them not worth keeping, as += 1 is not that much longer to type.

It did remove the ability to embed multiple actions within a single statement... Some may feel it makes the language less powerful... other will see that it makes code less confusing.

6 Likes

I for one am very glad that Rust does not have those auto increment operators.

They may have been useful in C if your compiler could not spot an opportunity to use an auto-increment instruction optimization then you had to tell it explicitly.

They have always been redundant in the language itself, apart from saving a few character to type.

Given the better ways we have to make loops and such in Rust auto-increment is even less useful.

They cause programmer confusion.

Just because C did a thing one way once upon a time does not mean that all subsequent languages have to blindly follow. I am very impressed at the way Rust rethinks many things we have taken for gospel for so long.

6 Likes

I wish this wisdom were followed more often with Rust. For example, the nastiness that is the unary-not operator (!). That should die a horrible death. :slight_smile:

3 Likes

Pray tell. What is wrong with the unary-not operator?

2 Likes

Hardly readable unless used with a separating space, but more importantly, it applies to the last expression of a chain of method calls:

//   /-----------------------------+
//  V                              |
if  !elems //                      |
        .iter() //                 |
        .filter(...) //            |
        .all(...)  // <------------+
{
    ...
}

which is a pity since whenever ::core::ops::Not is in scope (which it could always automatically be, if such trait was in the prelude), you can write a suffix .not() instead, for a more readable chain of method calls.

4 Likes

Why is "Not" not in the prelude?

2 Likes

I find ! to be more readable in certain cases, especially when looking at a small condition:

if !self.worked() { 
    println!("Oh no!");
}

But I also find both .not() and ! to be odd when looking at data flow through a large statement:

let foo = my_str
    .chars()
    .map(|c|
        my_list
            .iter()
            .map(|x| {...})
            .any(|(y, z)| {...})
            .not()
    )
    .collect::<Vec<usize>>();

But it also makes it more readable when looking at the end of an expression like @yandros's example, where the application is at the very end.

Another thing that I prefer ! for is factoring out a ! in an expression:

let x = ![1, 2, 3, 4, 5, 6, 7, 8, 9]
    .iter()
    .any(|x| x < 5);
// Turns into
let x = [...]
    .iter()
    .all(|x| x >= 5); //Negation inline here.

I find pros and cons to both forms.

3 Likes

This last transformation should turn any into all

6 Likes

Good catch.

I believe it can be useful shorthand to allow only prefix increment / decrement to quickly increment / decrement a variable. The restriction / difference to C/C++ would be to make the operator return (), so that it can only ever be used to apply a side effect to one variable, not to index something and then increment the index in one line like C++ allows:

// reads value at old index, but after this line index is incremented
some_array[index++];

Also no use of it in complex expressions:

// Is y 2x or 2x + 1? Was the expression evaluated rtl or ltr?
y = x + x++;

It would be useful for situations like incrementing the length of a collection internally:

impl<T> SomeCollection<T> {
    pub fn push(&mut self, value: T) {
        ...
        ++self.len;
    }
}
1 Like

I'm not personally convinced that using ++self.len is more readable than self.len += 1 but even if it is, is it really worth adding a new operator just for that?

2 Likes