Forbid -(unsigned integer)

We already have a lint item to find an application of the unary minus on an unsigned machine integer. Shouldn’t we simply forbid it? Rust programmers can always use 0 - x instead when they know what they are doing (rustc eventually translates -x into 0 - x because LLVM doesn’t have the unary minus.)

2 Likes

I don’t think it should be forbidden. Perhaps it would be a good candidate for a lint that could warn about constant expressions resulting in trying to eval a negative unsigned value (including a-b, where it’s known that b>a) unless in an unsafe block.

It’s really useful for bit twiddling - -1u is the easiest way to fill a uint with 1s, for example. Given that we’re a systems language it is important to support this kind of thing.

No, it is just a more obscure way of writing !0u. [playpen]

A good systems language should discourage people from writing such obscure codes. We should use arithmetic operators for doing arithmetic, bit operators for bit twiddling.

4 Likes

Obscure is relative - I think -1 is more common than ~0 for this purpose in C code, although the latter is pretty common as well.

Talking of C, it doesn't really yield an int with all bits set with one's complement number system :stuck_out_tongue_winking_eye:

Actually, signed->unsigned conversion in C is guaranteed to be treated as if two's complement were used:

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

You can’t use !0u64 in, for example, a match statement, like in this playpen link: http://is.gd/9Oic3G

In this case you could use std::u64::MAX instead.

While I appreciate the bitfiddly explanation here, this has caused people enough trouble for people that I’m inclined to just say we should kill it. As has been noted !foo, u64::MAX - foo and 0 - foo are all viable options for achieving the same results, depending on what you’re trying to accomplish. -foo is in my opinion the semantically worst one, because to many people it just looks like a logic error.

There are several idioms in C/C++ programming that Rust explicitly doesn’t support, presumably because they’re troublesome in practice. I see no reason to support this one.

4 Likes

I really have a hard time seeing how 0 - x is less of a logic error than -x or why it should be any different…

0 - x is using a binary subtraction operator. Something unsigned integers must support. -x is unary negation, something that doesn’t make a ton of sense for an unsigned integer to support unless you’re using it as a collection of bits, or a ring. These are of course perfectly valid use cases, and are fully supported by Rust.

However unless the domain clearly identifies the numbers in question as being used in such a manner, -x is something that I would expect to be a programmer error on a uint. 0 - x is something that is just trivially allowed by providing binary subtraction. I mean, we could lint against it too, but I don’t think that’s necessary.

I am highly doubtful that the core team is going to let unary negation of unsigned integers go away, though. It has been given the stamp of approval on several occasions before, and I don’t think they’re going to backtrack on it. It’s just going to be one of those errors tons of people make.

2 Likes

Just got bit by this using -1 as the step in range_step.

Is there a better way to reverse iterate an usize? This is how I'm doing right now

for pp in range_step((n - 1) as isize, 0, -1) { let p = pp as usize;

for pp in range(0, n).rev()

Patterns like 100, 96, 92, 88, … will be a bit non-intuitive if we write them by the combination of range() (or the range syntax) and Iterator::rev()

@nodakai, yeah this can be non-intuitive. (And while the returned iterators of range can be reversed, range_step iterators are not double-ended, which means the reversal of them cannot be iterated over.)

Maybe some enhancement can be made to range notations for this use case?

I believe the current important thing here is that by forbiding -(unsigned integer) we can avoid the hard-to-spot bug that @arthurprs encountered. (It is very hard to constantly stay aware of the value range of unsigned integers without the compiler’s help.) I propose that you file an RFC for this change.

I agree with disallowing this. As has been pointed out, there are several ways to express the same thing that are clearer, more explicit, and less likely to lead to errors from accidentally passing a negative constant as an unsigned quantity.

Also, if the RFC to consider wrapping a logic error by default is accepted, allowing unsigned negation would seem pretty weird.

This won’t work when/if the overflow checking is implemented, though, unless x is of type Wrapping<T>.

From my own experience doing bit fiddling (and I’ve been doing a lot of that lately), I prefer either 0xFFFF_FFFF_… or !0 and lack of -1u would’t bother me (0 uses across all code I wrote in Rust and other languages alike) a tiny bit.

1 Like

Right, the removal of the wrapping semantics by default will make it impossible to provide an handy but exact way to replace -unsigned_int in general (I don’t think people will find it convenient to write !unsigned_int + 1…)

const X: u32 = !10 + 1;
const Y: u32 = -10;

fn main() {
    println!("{}", X);
    println!("{}", Y);
}

[playpen]

@nodakai, personally I don’t find !unsigned_int + 1 to be annoying.

Also, If 0 - unsigned_int is an error due to overflowing, then it will be strange if -unsigned_int is not an error. (Because the programmer is requesting a value outside the valid range of unsigned ints, even though his/her true intention is !unsigned_int + 1.)

On the other hand, !unsigned_int is explicit bit flipping, where the programmer is not wanting a negative value. So it should always be a valid operation.

EDIT: Sorry I didn’t make myself clear. My opinion is that we should implement “error on overflowing” even if that means both 0 - unsigned_int and -unsigned_int will be invalid and we will have to write !unsigned_int + 1. (That is if other concerns doesn’t stop us from doing so.)