12 ms·
This kind of issue is the reason why some more modern languages like Rust or Go do not have implicit narrowing conversions. For instance, on Rust, trying to sim
by cesarb 5y ago
This kind of issue is the reason why some more modern languages like Rust or Go do not have implicit narrowing conversions. For instance, on Rust, trying to simply pass an usize (Rust's equivalent of size_t) to a function which expects an i32 (Rust's equivalent of int) will not compile; the programmer has to write "size as i32" (Rust's equivalent of "(int) size"), which makes it explicit that it might truncate the value at that point.
(Some Rust developers argue that even "size as i32" should be avoided, and "size.try_into()" should be used instead, since it forces the programmer to treat an overflow explicitly at runtime, instead of silently wrapping.)
- mnw21cam 5y agoAdd Java to that list.
- csharptwdec19 5y agoSame for C#. Any narrowing truncation needs to be an explicit cast. Widening is typically allowed implicitly, although in the case of the 'decimal' (128 bit struct representing a 'higher precision' floating point) type you still need an explicit cast from a 'double', since there are cases where that conversion can still change the value or fail (i.e. Infinity/NaN)
- pjmlp 5y agoMicrosoft is considering turning on by default checked arithmetic in the 2022 Visual Studio templates, by the way.
- paulddraper 5y agoNoo! This idea is super new and was invented by Go/Rust.
- irogers 5y agoIt can catch people by surprise that Java's narrowing conversions may not preserve the sign. For example, the following is broken: class TimestampedObject implements Comparable<TimestampedObject> { long timestamp; int compareTo(TimestampedObject other) { return (int)(timestamp - other.timestamp); } ... } ErrorProne catches this: https://errorprone.info/bugpattern/BadComparable https://errorprone.info/bugpattern/BadComparable
- mgraczyk 5y agoThat's cool! And some languages like Go don't even allow implicit widening conversions: https://play.golang.org/p/a5C5jsHypmu https://play.golang.org/p/a5C5jsHypmu
- est31 5y agoRust doesn't allow them either: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ba32a5a3108438d3dae8300ee6eb9dcc https://play.rust-lang.org/?version=stable&mode=debug&editio... Adding .into() works though, which is the recommended method if the conversion can be statically guaranteed (otherwise try_into should be used, which will become easier in the 2021 edition as the TryInto trait will become part of the prelude).
- ridiculous_fish 5y agointo() does not work from size. It's rather frustrating in practice. https://stackoverflow.com/questions/62832438/why-is-rusts-usize-to-u128-conversion-considered-failable https://stackoverflow.com/questions/62832438/why-is-rusts-us...
- masklinn 5y ago> It's rather frustrating in practice. All explicit conversions, more so fallible, are "frustrating in practice" especially when coming from a language without those foibles. But given the semantics of usize/isize, it is perfectly reasonable, nay, a good thing, that they're considered neither widenings nor narrowings of other numeric types.
- ridiculous_fish 5y agousize should be Into<u64> iff usize is <= 64 bit. usize is already on that hook: `0x100000000usize` will fail to compile in 32 bit, so you already risk compile errors when switching archs. As it is I'm just writing `as u64` which is clearly worse.
- 5y ago
- derefr 5y ago> Some Rust developers argue that even "size as i32" should be avoided, and "size.try_into()" should be used instead, since it forces the programmer to treat an overflow explicitly at runtime, instead of silently wrapping. It's important to still have the option for efficient truncating semantics, though; some software (e.g. emulators) needs to chunk large integers into 2/4/8 smaller ones, and rotation + truncating assignment is usually the cheapest way to do that. But, importantly, this is a rare case. Most software that does demoting casts does not mean to achieve these semantics. So I wonder — are there any low-level/systems languages where a demoting cast with a generated runtime check gets the simple/clean syntax-sugared semantics (to encourage/favor its use), while truncating demotion requires a clumsier syntax (to discourage its use)?
- titzer 5y agoIn Virgil, casts are written "type.!(expr)". Casts between numbers check ranges and roundability (for float<->int conversion). Reinterpreting the bits is written "type.view(expr)" and ignores signs, looks at the raw float bits, etc. edit: a cast will throw an exception if it fails, in case that was not clear from context.
- londons_explore 5y agoIt would be nice if CPU's had an instruction for "read the low 8 bits of this register, and require the high bits all be zeros (otherwise throw an exception)". Then safety is free...
- derefr 5y agoWhat does "otherwise throw an exception" mean at the CPU level? I know Erlang's BEAM VM has a "fail jump pointer register", where instructions that can fail have relative-jump offsets encoded as immediates for those instructions, and if the instruction "fails" in whatever semantic sense, it takes the jump. But most CPUs don't have anything like that. Would you want it to trap, like with integer division by zero? CPU traps are pretty hard to handle in most language runtimes, such that most compilers generate runtime checks to work around them, rather than attempting to handle them.
- ronsor 5y agoShout out to Zig, which requires explicit casting also.
- titzer 5y agoThis is a deep hole for language design. I thought about this very, very carefully when designing Virgil[1]'s numerical tower, which has both fixed-size signed and unsigned integers, as well as floating point. Like other new language designs, Virgil doesn't have any implicit narrowing conversions (even between float and int). Also, any conversions between numbers include range/representability checks that will throw if out-of-range or rounding occurs. If you want to reinterpret the bits, then there's an operator to view the bits. But conversions that have to do with "numbers" then all make sense in that numbers then exist on a single number line and have different representations in different types. Conversion between always preserve numbers and where they lie on the number line, whereas "view" is a bit-level operation, which generally compiles to a no-op. Unfortunately, the implications of this for floating point is that -0 is not actually an integer, so you can't cast it to an int. You must round it. But that's fine, because you always want to round floats to int, never cast them. [1] https://github.com/titzer/virgil https://github.com/titzer/virgil
- cjensen 5y agoC/C++ compilers commonly have warnings for narrowing conversions, and separate warnings for mixing signed/unsigned conversions for same-sized values. While some folks aren't too fussed about warnings like this, those folks generally aren't writing secure code like kernels. I'm very surprised that kind of conversion was permitted in the code.
- baby 5y agoI wish clippy had a lint against downcasts specifically. I aso like that rust has no “int” type
- jamincan 5y agoIt does, you just need to enable it. There are a bunch of other cast-related lints as well that are allow by default. https://rust-lang.github.io/rust-clippy/v0.0.212/#cast_possible_truncation https://rust-lang.github.io/rust-clippy/v0.0.212/#cast_possi...
- baby 5y agoOh wow, didn’t know about that one. Is this new?
- WalterBright 5y agoD doesn't allow implicit narrowing conversions. The user has to have an explicit cast, like `cast(int) size`. Cast is made into a keyword so all those explicit conversions can be found with a simple grep. We consider it best practice to try and organize the types such that explicit casts are minimized.
- pjmlp 5y agoLanguages of the same age or older than C, also have explicit narrowing, but apparently that was seen as programming with a straightjacket.
- mytailorisrich 5y agoThe compiler can issue warnings for this. This os why in C it is a good practice to enable all compiler warnings and to have the compiler treat warnings as errors.
- viraptor 5y agoIn theory but not in practice if you're distributing your apps sources. If you write for C compiler Foo 8, there's a decent chance Foo 9 will raise a warning which didn't exist before. Now you have to handle "why doesn't this compile" issues and distributions have to patch your sources to do future releases. And that's ignoring bugs like GCC in the past where in some versions you could not satisfy specific warnings.
- j1elo 5y agoThe golden rule is: use -Werror for Debug builds, so you catch and fix all warnings during development. That's fair enough. But never ever leave -Werror enabled for building in Release mode. You'll be preventing your code from building as soon as a new compiler version goes out. Maintainers or code archeologist will have a much worse time than if this option was simply disabled to start with.
- nly 5y agoLong and int are the same size on Windows. The fact that these sizes aren't well defined is the cause of the issue.
- phkahler 5y agoThis wouldn't be a problem if "int" was defined as the same size as size_t. The solution is probably to change all those functions to take a parameter of size_t instead of int. IMHO one should always be using C99 types instead of int, but Linux predates that. Also, shouldn't that implicit conversion cause a compiler warning?
- kenniskrag 5y agoso you mean int_fastX_t or int_leastX_t?
- tialaramex 5y agoLinux apparently provides a similarly named set of sized integer types to Rust, ie: s8 u8 s16 u16 s32 u32 s64 u64 But of course getting C programmers to use these integer types rather than the ones they grew up with isn't easy.
- foobiekr 5y agoI did a lot of C - a lot - in the mid-90s through the mid/late 2000s and have never seen any sizable C code base where explicit sizes were not the norm throughout - u_int32_t etc. I think this is not the problem, it's the implicit conversions.
- masklinn 5y agoI don't think that stops C's implicit conversions either, does it?
- dezgeg 5y agoIt doesn't. They are in the end just typedefs to the basic types like int, long, etc.
- cesarb 5y ago> This wouldn't be a problem if "int" was defined as the same size as size_t. That would lead to a hole in the type sequence (char <= short <= int <= long <= long long) for 64-bit targets (where int is the 32-bit type while size_t is 64 bits). > IMHO one should always be using C99 types instead of int, but Linux predates that. On the other hand, on Linux "long" has always been defined as the same size as size_t, so using "long" instead of "int" everywhere could also be an option.
- marcosdumay 5y agoI think Rust has no language construction for that, but the best implementation of "size as i32" should fail on overflow. In Haskell I would use an exception, and mark the function as unsafe, but the stdlib seems to disagree with me here.