Hacker News new | past | comments | ask | show | jobs | submit login

And now I see that HackerNews ate my multiplication symbols. What I meant to type is that

    unsigned short a = 0xFFFF;
    unsigned short b = a;
    unsigned short c = a * b;
Is currently undefined behaviour on platforms where int has more bits than short (like x64 and arm) due to the interaction between the integer promotion rules and undefined integer overflow.



Rust here says OK, we'll define Mul (the * operator) for the same type (and for references to that type) so

  let a: u16 = 0xFFFF;

  let b: u16 = a;

  let c: u16 = a * b;
... is going to overflow, Rust would actually detect that because 0xFFFF is a constant, so, this says "Silently do overflowing arithmetic" and er, no, it doesn't compile. However if you achieved the same thing via a blackbox or I/O Rust doesn't know at compile time this will overflow, in a Debug build it'll panic, in a Release build it does the same thing as:

  let c: u16 = a.wrapping_mul(b);
Because the latter is not an overflow (it's defined to do this), you can write that even in the constant case, it's just 1, the multiplication evaporates and c = 1.

In C++ if you can insist on the evaluation at compile time there is no UB, so you get an compiler error like Rust.


I don't like Rust's approach, but it is better than C's. Rust should either commit to wraparound or make the default int type support arbitrary values.

In C, the problem isn't the silent wraparound, the problem is that when the compiler sees that expression, it will assume that the resulting value is less than INT_MAX, and optimise accordingly. The other insidious problem is that wraparound is defined for other unsigned arithmetic, so a programmer that hasn't had this explained to them, or read the standard very carefully, would quite easily assume that arithmetic on unsigned short values is just as safe as it is for unsigned char, int or long, which is not the case.


I understand why you don't like C's behaviour here.

> Rust should either commit to wraparound or make the default int type support arbitrary values.

Committing to wrapping arithmetic everywhere just loses the ability to flag mistakes. Rust has today Wrapped<u32> and so on for people who know they want wrapped arithmetic. I'd guess there's a bunch of Wrapped<u8> out there, some Wrapped<i8> and maybe some Wrapped<i16> but I doubt any large types see much practical use, because programmers rarely actually want wrapping arithmetic.

The mistakes are real, they are why (thanks to whoever told me about this) C++ UBSAN in LLVM actually flags unsigned overflow even though that's not actually Undefined Behaviour. Because you almost certainly weren't expecting your "file offset" variable to wrap back to zero after adding to it.

For performance reasons your other preference isn't likely in Rust either. Type inference is not going to let you say "I don't care" and have BigNums in the same code where wrapping is most dangerous.

We can and should teach programmers to write checked arithmetic where that's what they meant, and Rust supports that approach much better than say C++. Also the most serious place people get tripped up is Wrangling Untrusted File Formats and you should use WUFFS to do that Safely.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: