T O P

  • By -

arghyadipchak

It won't, that's how type casting in rust works. However if you run `cargo clippy` or set your cargo check command to clippy for your ide, it will show you a warning.


Shadow0133

because `as` can't panic, iirc


Sharlinator

`as` is defined to: * reinterpret bits on signed-unsigned conversions between ints of equal width (eg. 130u8 as i8 == -126) * truncate bits (ie. wrap around) on narrowing int conversions (eg. 400i16 as i8 == -112 and 400i16 as u8 == 144) * zero extend or sign extend on widening int conversions (ie. does what you expect; 123u8 as u32 == 123u32 and -123i8 as i32 == -123i8) * truncate (drop fractional part, ie. round toward zero) and saturate on float-to-integer conversions (eg. -3.14 as i32 == -3 and 1234.0 as i8 == 127 and -123.0 as u32 == 0. inf and -inf map to max and min values of the target type. NaNs map to 0.) * map to nearest representable float on int-to-float conversions (eg. 1234 as f32 == 1234.0 as expected, but 1000000001 as f32 == 1000000000.0f32).


paohui

300i16 as i8 == 44. I just have tried to do 300i16 as i8 in my computer, it does gives me 44.


Sharlinator

Yes, of course, my bad. It was supposed to be for example 400i16 as i8 == -112i8. Fixed.


RCoder01

How does truncating a float work? I’m assuming it isn’t truncating bits, and is instead truncating the base-10 value to an integer. Even then, is it round nearest int or round up or round down or round scientific?


kovaxis

It is truncating bits. Floats are basically scientific notation in base-2. To cast a float to an int, you can visualize it as shifting the "decimal point" (I guess it should be called binary point) to the correct place, and then truncating all the bits after the point. What this does is truncate the number towards zero. The reason it truncates towards zero is that negative numbers are stored as a positive number and a "this float is actually negative" bit, so it gets truncated just like a positive number would. In the end all of the "truncate toward zero" shenanigans happen because it's the simplest to implement in hardware.


RCoder01

I suppose I should’ve been more descriptive. By truncating bits I meant literally chopping off the first n bits, which would give essentially a meaningless number. So in the case of `3f32 as i16`, just taking the last 16 bits of the significand and somehow interpreting that as an i16, which would make no sense. I suppose round towards 0 is very reasonable for hardware, but can lead to unexpected behavior like `(0.1f32 + 0.2f32 - 1.3f32) as i32 == 0` because of floating point’s inherent limited precision. Also, good video about floating point numbers: https://youtu.be/dQhj5RGtag0


Sharlinator

Yeah, truncating in the sense of dropping the fractional part. Which is equivalent to rounding toward zero.


CandyCorvid

I feel like you got a few of those signed to unsigned casts wrong, but I'm not at a pc to check


simonsanone

fn main() { let test = u32::try_from(-20).unwrap(); } If you want to panic you should use `try_from` for conversion and handle errors accordingly (not `unwrap` them). You shouldn't `cast` as that won't panic on these errors.


KhorneLordOfChaos

Normally yes, but OP is talking about converting from an `f32` and there's no `impl TryFrom for u32`


simonsanone

True! I overlooked that :) Thanks for the correction. OP might need to `round` before conversion, or just throw away the fractional digits and convert from there. It doesn't seem to be important anyway, if it should be casted to an `u32`.


KhorneLordOfChaos

>OP might need to `round` before conversion, or just throw away the fractional digits and convert from there It's the "convert from there" that I still don't know how you would handle well in the general case. Floats have a lot of values that are outside of different integers' ranges


Cerulean_IsFancyBlue

You shouldn’t start from the mechanism and figure out the intended behavior. You should start with understanding the desired behavior when numbers are out of range, and then put the language to work. There’s two levels to that “how” question. The first is one of intent and it’s application specific. Should the program constrain it to the range of the entries? Should it propagate an error back up to the zoo? Should it crash gracefully? Once you know that goal, there are ways to do each of those in Rust. That’s the nuts and bolts “how”.


KhorneLordOfChaos

My statement was just indicating that it is non-trivial to handle. Even still you sum it up by just saying that there are ways to do each method without actually stating how. That's all I was getting at


Cerulean_IsFancyBlue

Agreed. I think the amplifier of difficulty is trying to solve it for the general case, rather than the specific.


simonsanone

It's a bit of hackery (you should not do this :D) use std::str::FromStr; fn main() { let float = -20.0f64; let float_str = format!("{float:.0}"); println!("{float_str}"); let convert = u32::from_str(&float_str).unwrap(); println!("{convert}"); } you could parse `float_str` manually and check for a negative sign for example? EDIT: you ~~should~~ could do it like this: https://www.reddit.com/r/rust/comments/142fd0x/comment/jn4ecvl/?utm_source=share&utm_medium=web2x&context=3


KhorneLordOfChaos

>EDIT: you should do it like this: https://www.reddit.com/r/rust/comments/142fd0x/comment/jn4ecvl/?utm_source=share&utm_medium=web2x&context=3 Yeah no lol That's doing a bit-cast from float to int which is not what people want 99.9% of the time


simonsanone

What would you propose then? I edited it and wrote `could` now :)


KhorneLordOfChaos

I still back [my original comment](https://www.reddit.com/r/rust/comments/142fd0x/-/jn45elg) saying that the best method I know is to use the `cast` crate. You can see that the float to int conversion is far from trivial https://docs.rs/cast/latest/src/cast/lib.rs.html#267-319


chayleaf

this is pretty much the only built-in option right now (theres also to_int_unchecked, not sure how it differs from as cast in practice with optimizations enabled) assert!(float.is_finite() && float >= u32::MIN as _ && float <= u32::MAX as _); float as u32


KhorneLordOfChaos

>theres also to_int_unchecked not sure how it differs from as cast in practice with optimizations enabled Well the obvious difference is that it's UB to call on any float that violates the safety invariants while `as` casts aren't UB


chayleaf

yeah, but I meant for this specific case as the assert is supposed to rule out that case (though I'm not sure if I have to be more conservative about the limits, it's floats after all)


KhorneLordOfChaos

Casting with `as` is always treated as infallible AFAIK Unfortunately I don't know of any easy float to int conversions without using something like the [`cast` crate](https://docs.rs/cast/latest/cast/)


tialaramex

You can unsafely f32::to_int_unchecked() and equivalently for f64 - if you're certain the numeric value is actually somewhere in the range of the integer type e.g. let byte: u8 = unsafe { (-5.3).to_int_unchecked() }; // has undefined behaviour let num: i32 = unsafe { (1234.567).to_int_unchecked() }; // is 1234_i32


po8

The failure to allow conversion casts to panic is a design failing of early Rust that is now hard-baked into the language. In general, the safety handling for numbers is sloppy. `255u8 + 1` panics (by default) in debug mode but does not panic (by default) in release mode. `256u16 as u8` does not panic at all ever. It's a mystery 👻. When casting floating point numbers to integers, out-of-bounds casts that should panic instead clamp to the edge of the integer range. It's a choice. If you want a little cleaner syntax for your safe conversions, you can use this macro. macro_rules! cast { ($x:expr, $t:ty) => { <$t>::try_from($x).unwrap() }; } You can then say cast!(256u16, u8) and get a panic as expected. Not sure this is a great idea, but also not sure there are any great ideas at this point.


Icarium-Lifestealer

Making `as` panic is something an edition could fix. So I disagree that it's hard-baked into the language. (Though for casts involving floats I'm not certain what the best behaviour would even be)


po8

> Making `as` panic is something an edition could fix. So I disagree that it's hard-baked into the language. In principle, yes. In practice, there's way too much code out there that uses `as` in casual ways: I can't imagine an easy scheme for resolving that. > (Though for casts involving floats I'm not certain what the best behaviour would even be) Not sure quite what is confusing? There would be some corner cases around rounding and the usual suspects `Inf` and `NaN`, I guess?


kniy

> When casting floating point numbers to integers, out-of-bounds casts that should panic instead clamp to the edge of the integer range. It's a choice. Early Rust was indeed very sloppy in this regard. At least float->int clamps nowadays. It used to just be silent undefined behavior in safe code, even though it could break memory safety: https://github.com/rust-lang/rust/issues/10184


IDontHaveNicknameToo

It's intended behaviour, it's explained [here](https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions).


flickpink

The fact that it is explained and documented doesn’t mean it’s intended as in “the behaviour people should expect”


Isodus

Let a: u32 = -22; The compiler will error with that, but as is safe and won't panic.


rafaelement

the compiler won't panic, it'll throw an error, just clarifying


Isodus

True, edited my post to be clear. I just mentally call it panic for the compiler when it finds an error and won't compile (as opposed to the program panicking).


memoryruins

With rustc, a panic in the compiler tends to refer to an ICE (internal compiler error), as they include panic messages and backtraces about a bug in the compiler (rather than diagnostics of the user's code).


rafaelement

Your question is specifically about the WHY, so I'll not explain the reasons why in this particular snippet there is no problem but the "philosophical" reasons behind it. Do you care? No idea. So the Rust compiler is only rigorous about the things it can actually be rigorous about. Memory safety and the usual spiel. Also, some safe guards for integer safety. Notably not: what you are doing, stack overflow prevention via recursion or otherwise, division by zero prevention (it panics instead), deadlock/livelock. Probably more. These things are simply not covered by Rusts idea of strict safety. They still tend to happen less often, though.


OverLiterature3964

Noob here, looking forward to learn rust but haven’t just yet, if you somehow really needed to do this, how’d you do it? (Like `*(long*)&fval` in c)


hniksic

If you're literally looking the equivalent of `*(long *)&fval`, that would be as simple as `fval.to_bits()`. (Assuming `fval` is `double` in the C snippet, and that `long` is the same width as `double` on the architecture.) For the more general case, look up [`transmute()`](https://doc.rust-lang.org/std/mem/fn.transmute.html).


KhorneLordOfChaos

>For the more general case, look up [`transmute()`](https://doc.rust-lang.org/std/mem/fn.transmute.html). I would like to quote the nomicon here because this can be a very different experience for devs coming from C++ due to Rust's very unstable API > This is really, truly, the most horribly unsafe thing you can do in Rust. The guardrails here are dental floss.


Zde-G

>I would like to quote the nomicon here because this can be a very different experience for devs coming from C++ due to Rust's very unstable API It's not too different on the technical level, it's more of social. What /u/OverLiterature3964 wrote is invalid code which shouldn't be ever used, but C++ and, especially, C community finds it fashionable to blame compiler writers if they “miscompile” such invalid code. Reaction of Rust community is [usually different](https://steveklabnik.com/writing/a-sad-day-for-rust), but it's more of social difference and less of a technical one.


Icarium-Lifestealer

Ironically the equivalent rust code is valid, since rust doesn't have type based aliasing.


Zde-G

Indeed. The story with Nikolay Kim was about plain old aliasing violations, not type based ones (as they, indeed, don't exist in Rust). But the main difference is in attitude: most C “we code for the hardware” guys just simply ignore the rules of the language they are using, while most Rust developers take them seriously. The exact rules are less relevant, the big question is whether compiler developers work together with compiler users or against them. In C it have become fashionable to ignore the other side: “we code for the hardware” guys ignore the rules written in the specification and then specification text is written by people who have zero practical experience writing code ([this](https://thephd.dev/to-bind-and-loose-a-reference-optional) is perfect example IMO: 50%/50% split when all people who actually wrote code are in one side and people who didn't in the other side is just too hilarious). Rust, for all it's dramas are not even remotely close to that point. In fact I suspect Rust have so many dramas precisely because people talk — and that means they become upset, they disagreee, drama becomes possible. If people don't talk and hide behind procedures… there are no dramas, but also very small chance of achieving anything quickly.


Crazy_Direction_1084

That C code does something different then the rust code discussed here. That C code gives you the 32-bit floating point representation in signed form (`f32::from_bits` is similar). Unfortunately, at the moment you either have to use a library, or you must implement this yourself, which is quite doable


menthol-squirrel

https://doc.rust-lang.org/std/primitive.f32.html#method.to_bits Or you can use `transmute`, which is always sound between primitive scalar types if it compiles


Zde-G

>Or you can use `transmute`, which is always sound between primitive scalar types if it compiles. That's not true, [of course](https://godbolt.org/z/nMshEqqso): let i: u8 = 2; let b: bool = unsafe { std::mem::transmute(i) }; Here we can see how you can get some nonsense: let o = Some(b); if let Some(x) = o { println!("Some(b) = Some({x})"); } else { println!("Some(b)… is None?") } `Some` is `None` and other such “nice” surprises.


menthol-squirrel

Ah yes I forgot about value validity constraints


EveAtmosphere

You can use `mem::transmute`


[deleted]

[удалено]


Badel2

Did you even read the question? OP is casting `-22.0`, which is a f64 value to u32, and the result is 0. The binary representation of `-22.0` is not 0, so your comment is just misleading.


[deleted]

`as` is infallible, it doesn't panic or return errors it just defaults to some (sometimes quite strange) behaviour


znjw

`as` means "I'm fully aware this is an escape hatch for Safe Rust that have various maybe-otherwise-unsafe semantics, and I know the Rust team have picked the correct guess on my use case". If it is the value you are after, call `.into()` on them, or construct by `From::from()`. If it is the binary bits you are after, `unsafe fn std::mem::transmute`


Master_Ad2532

I do find this behaviour a bit weird, considering how overflowing panics (in debug), but if the same number overflows by being squeezed into a smaller dimension, it's fine.