Pre-RFC: x++ and x-- operator for clarity

Summary

Add an n++ operator, but not a ++n operator, to Rust.

Motivation

Rust already has n+=1, which is the ++n operator. This is increment first, then return the incremented value.

Increment and then return the un-incremented value is a more verbose affair requiring the allocation of a temporary variable.

In Rust, allocating something like an index from a free list can be done as such:

    fn allocate(&mut self) -> u16 {
        if let Some(id) = self.free_list.pop() {
            id
        } else {
            let id = self.counter;
            self.counter += 1;
            id
        }
    }

This could be more concise:

    fn allocate(&mut self) -> u16 {
        if let Some(id) = self.free_list.pop() {
            id
        } else {
            self.counter++
        }
    }

Explanation

Because Rust provides n+=1 already, we don't need ++n. Besides redundancy, ++n and n++ look similar; if the goal is code readability by removing unnecessary verbosity, then implementing only n++ gives two clearly-different statements, avoiding the need to remember the semantic difference for whether the ++ is before or after the variable.

In other words, ++n is unnecessary and so implementing only n++ gives the unary ++ operator exactly one meaning, instead of two possible meanings. This meaning is also slightly more immediately recognizable than three lines of syntax with two different variables interspersed.

Do note foo = a + (n+=1) is not legal and there's no particular reason foo = a + n++ should be legal either. Such syntax can be problematic, especially in weird uses like foo = a + n++ + b*(n++) - (n++)/(n++). It's probably best to just treat n++ as an assignment (i.e. ++ is an assignment operator like +=).

There are other syntaxes that could express the same, such as n=+1 and n=-1, but -1 has its own meaning and typographical errors are much more likely than with ++ and --. Another possibility is to allow syntax like n++2 which would add 2 to n, essentially making ++ equivalent to += but returning the pre-assignment value; whether to allow the unary n++ to imply n++1 is a judgment call in that case.

This is a frequently-requested feature that Rust purposefully doesn't include. From the 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.

I do want to point out one thing:

The reason a + (n+=1) isn't legal is that the return type of an assignment is (). Therefore, treating n++ as an assignment would prevent the very thing you want to use it for.

I do miss post-increment sometimes. Although perhaps the practical thing would be to use "defer" for this; in current Rust, with the scopeguard crate, we could implement your function as:

    fn allocate(&mut self) -> u16 {
        self.free_list.pop().unwrap_or_else(|| {
            defer!{ self.counter += 1; }
            self.counter
        })
    }
10 Likes

A post_add method could fill the role.

2 Likes

The reason a + (n+=1) isn't legal is that the return type of an assignment is (). Therefore, treating n++ as an assignment would prevent the very thing you want to use it for.

I think you may have not actually read the post. From the part you quoted:

Do note foo = a + (n+=1) is not legal and there's no particular reason foo = a + n++ should be legal either. Such syntax can be problematic

The feature I described doesn't require any knowledge of evaluation order because what I described has the same exact rules and can occur only in the same exact places as n+=1.

In Rust, the only restriction on where n+=1 can appear is that it must be somewhere you can put an expression of type (). For example, this is perfectly legal (though bad) Rust:

    let mut n = 0;
    let a = ();
    let foo = a == (n+=1);

So if you say that self.counter++ returns u16 but can't be used somewhere a u16 can be used, that would be an additional restriction that is not part of present Rust.

3 Likes

Oh, that makes sense. Somehow I thought I'd seen code that used n+=1 as a return value for a function returning u16. That's my fault. `

On unstable it's almost simpler to use an atomic here:

        self.free_list.pop().unwrap_or_else(|| {
            AtomicUsize::from_mut(&mut self.counter)
                fetch_add(1, Ordering::Relaxed)
        })

Bad code in another way, but I find it remarkable and slightly amusing precedent for an operation that 'returns the old value' of its operand.

Even when coding in C++ I would implement the allocate function without the post-increment because the evaluation order of that is just too confusing.

1 Like