Rust does not support ++ and -- operator

Please tell me Why rust does not support ++ and -- operator.

1 Like

Possibly because they are confusing (with prefix and postfix versions) and in cases where you don’t need the return value in an expression, you can easily use x += 1 or x -= 1, too.

If you really want an exact equivalent (in terms of behavior), I guess you can write things like {x+=1; x} or {x+=1; x-1} instead of ++x or x++.

Finally, every programming languages has its arbitrary choices over how operators work, which ones exist and how customizable they are, so in a sense it may just be pretty much arbitrary; or personal preference of the people that created early versions of Rust.

9 Likes

From the Rust FAQ:

Why doesn't Rust have increment and decrement operators?

Preincrement and postincrement (and the decrement equivalents), while convenient, are also fairly complex. They require knowledge of evaluation order, and often lead to subtle bugs and undefined behavior in C and C++. x = x + 1 or x += 1 is only slightly longer, but unambiguous.

7 Likes

Can I turn this question around and ask: Why would any programming language ever support such an obscure syntax?

As far as I can tell ++ and -- originate in the C language. And it's only in the C language because the PDP-8 or whatever computer they developed it on had instructions that did pre/post increment/decrement instructions. C, being a step up from assembler, incorporated that as an optimization.

Correct me if I am wrong.

Outside of that I see little sense in it.

It's a confusing syntax that only serves to save a character or so of typing.

5 Likes

x = x + 1 or x += 1 is only slightly longer, but unambiguous.

It is inconvenient in generic code though, because you can't just use a literal 1 anymore. That's part of why we have the (still unstable) Step trait to make generic ranges work.

11 Likes

Oooh that means we could later desugar ++i to { i.forward(1); i } ?

Once I was working on a project using the Processing language (Java-based). I rememeber when I used some variable and then after that ++ inside in array (for example array[x++]). I remember I got screwed over big time and I spent quite some time figuring out why something was going wrong (I can't quite remember as this was a couple of years back) but then I realised that x++ was being incremented AFTER the array was indexed the original value of x.

Rust favours security over convenience. Rust does not want you to make silly little mistakes than can waste so much of your time debugging, which in the end makes it more convenient.

2 Likes

It's also a very common operation when you canonically use indices to loop.

One of my instructors, due to their own experiences, hammered into me the avoidance of post-increment (or decrement) as it involves an extra copy. Then C++ went and made things worse by making post-increment seem like the more natural option! Somehow those biases stuck with me, though I'm sure any modern compiler can deal with ease...

On the other hand, I also feel a justifiable part of the bad rep comes from confusions between the pre- and post- forms like @Joe232 mentions. So there's one argument for having one but not both. (Not that I think we'd ever get it, which is fine by me.)

Backwards-compatibility-wise, the pre-decrement boat has already sailed.

let x = 2;
let y = --x;
assert_eq!(y, 2);
2 Likes

From C History:

For example, B introduced generalized assignment operators, using x=+y to add y to x. The notation came from Algol 68 [Wijngaarden 75] via McIlroy, who had incorporated it into his version of TMG. (In B and early C, the operator was spelled =+ instead of += ; this mistake, repaired in 1976, was induced by a seductively easy way of handling the first form in B's lexical analyzer.)

Thompson went a step further by inventing the ++ and -- operators, which increment or decrement; their prefix or postfix position determines whether the alteration occurs before or after noting the value of the operand. They were not in the earliest versions of B, but appeared along the way. People often guess that they were created to use the auto-increment and auto-decrement address modes provided by the DEC PDP-11 on which C and Unix first became popular. This is historically impossible, since there was no PDP-11 when B was developed. The PDP-7, however, did have a few `auto-increment' memory cells, with the property that an indirect memory reference through them incremented the cell. This feature probably suggested such operators to Thompson; the generalization to make them both prefix and postfix was his own. Indeed, the auto-increment cells were not used directly in implementation of the operators, and a stronger motivation for the innovation was probably his observation that the translation of ++x was smaller than that of x=x+1.


As for whether it is useful to have pre/post increment/decrement operator in modern languages -- I don't think so. They're useful when doing raw pointer access (like while (*p++) ...) which higher languages have better abstractions for (e.g. iterators or actual string types).

I definitely don't miss it in Rust/Python and in C++ I tend to use these operators only on iterators (which are pretty much pointers) and only the pre-increment since semantically that's the operation I want (even though the post-increment would optimise to the same code).

3 Likes

Because it is ambiguous and for me, stupid to be introduced into any language. For example, this C code:

int x = 1;
some_function(x++, x++);

What is the first and second concrete argument for some_function?

Actually it is UB. They can be 1,2 respectively, or 2, 1 respectively, depending on the compiler's implementation. And you've got many UBs with the increment operator there.

Expressions with side effects are just anti-patterns.

2 Likes

Or 1,1, if both values are first passed to function and then incremented. Or the whole branch containing this call can be compiled into no-op.

1 Like

Such operators introduce a potentially ugly order-of-execution problem. Consider the following bit of C/C++ code:

int main() {
    int i = 1;
    i += i++ + ++i;
    printf("i = %d\n", i);
}

This was on the whiteboard when I showed up for an interview some decades ago. My answer was "that's a really ugly piece of code; please never ship it!"

Apparently, that answer was well-received, because I got the job (and still have it today).

The problem is that C and C++ are not well-defined in terms of their interpretation of expressions with side effects, such as this. There are three places where the compiler can decide which side of the expression to evaluate first (both ++ operators and the += operator). So different compilers could yield different results. Even the same compiler could yield different results depending on which way the wind blows.

Rust avoids that problem by not offering operators that have that kind of ambiguity.

I agree that it's wise to avoid operators like that, but because any block can be part of an expression, rust still allows for similarly confusing code. The ambiguity is resolved by having a well-defined order of operations, but that doesn't help the human reader who can't hold that order of operations in their head.

x += { x = x+1; x} + x + { let y = x; x = x+1; y};

You can still write the heinous code, it just looks even more heinous...

2 Likes

You can even cheese this on stable :smiling_imp:

fn succ<T>(x: T) -> T where std::ops::RangeFrom<T>: Iterator<Item = T> {
    (x..).nth(1).unwrap()
}

https://rust.godbolt.org/z/W4795T

6 Likes

However this rust code also depends on the order of evaluation (which AFAIK isn't properly defined yet):

fn main() {
    let mut v = vec![1, 2];
    foo(v.pop().unwrap(), v.pop().unwrap());
}

fn foo(a: i32, b: i32) {
    println!("{}, {}", a, b);
}

I agree that ++ and -- operators can lead to pretty confusing situations and as such should be avoided, however I don't think the order of evalutation is the reason.

IIRC some time ago, there was a discussion about borrowck that mentioned how evaluation order of function arguments has to be left-to right, because things like foo(&vec, vec.pop()) compile today, and changing that would thus be breaking.

(Well, thinking about it, exactly that doesn't compile, but you get the idea.)

2 Likes

The order is properly defined. But it seems the documentation for it is merged just yesterday and not rendered yet to the official hosting.

10 Likes

Point taken. But, as you say, it at least looks heinous. :slight_smile: