What is the difference between as and From for casting?
Short answer: as casts between any two primitive number types and never fails — when the value does not fit, it silently keeps the low bits. From is only implemented where the conversion is always exact, so i64::from(x) compiling is the proof that nothing was lost. Narrowing that might not fit gets TryFrom, which returns a Result you must handle instead of a wrong number.
From a fresh project — cargo new cast-demo, then replace src/main.rs with each block below.
What as does when the value does not fit
Section titled “What as does when the value does not fit”300 is bigger than a u8 can hold (0–255), and as casts it anyway:
fn main() { let n: i32 = 300; println!("300i32 as u8 = {}", n as u8); let m: i32 = -1; println!("-1i32 as u8 = {}", m as u8);}300i32 as u8 = 44-1i32 as u8 = 255as between integers keeps the low bits and throws the rest away: 300 in binary is 100101100, a u8 keeps the last 8 bits, and 00101100 is 44. A negative number loses its sign bit the same way — −1 becomes 255. The compiler accepts all of it, and nothing at run time warns you. (The 64-bit version of this wrap — as usize producing a 19-digit number — is the subject of How to choose between i32 and usize.)
Floats to ints: truncation with a clamp
Section titled “Floats to ints: truncation with a clamp”Float-to-integer casts truncate toward zero, and — instead of wrapping — clamp to the target type’s range:
fn main() { println!("2.9 as i32 = {}", 2.9f64 as i32); println!("1000.0 as u8 = {}", 1000.0f64 as u8); println!("-2.7 as i32 = {}", -2.7f64 as i32);}2.9 as i32 = 21000.0 as u8 = 255-2.7 as i32 = -2The decimal part is dropped, never rounded — 2.9 becomes 2. And a value far outside the target’s range becomes the nearest edge (1000.0 → 255) rather than a wrapped-around remainder, which keeps as on floats from producing the kind of nonsense as on integers just did. But it is still a silent, lossy cast: nothing tells you the value changed.
From only exists when nothing can be lost
Section titled “From only exists when nothing can be lost”From is implemented for exactly the conversions that always succeed — the widening ones:
fn main() { let big = i64::from(42i32); let wide = u32::from(200u8); println!("{big} {wide}");}42 200Every i32 fits in an i64, and every u8 fits in a u32, so those conversions are infallible and From exists for them. The narrowing direction has no From at all, and the absence is the feature — this does not compile:
fn main() { let x: u8 = u8::from(300i32); println!("{x}");}cargo run -qerror[E0277]: the trait bound `u8: From<i32>` is not satisfied --> src/main.rs:2:17 | 2 | let x: u8 = u8::from(300i32); | ^^ the trait `From<i32>` is not implemented for `u8` |help: `u8` implements trait `From<T>` --> /Users/rango/.rustup/toolchains/1.99.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ascii/ascii_char.rs:1168:13 |1168 | const impl From<AsciiChar> for $ty { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `From<std::ascii::Char>`...1178 | into_int_impl!(u8 u16 u32 u64 u128 char); | ---------------------------------------- in this macro invocation | ::: /Users/rango/.rustup/toolchains/1.99.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/convert/num.rs:37:9 | 37 | const impl From<bool> for $int { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `From<bool>`... 58 | impl_from_bool!(u8 u16 u32 u64 u128 usize); | ------------------------------------------ in this macro invocation = note: this error originates in the macro `into_int_impl` which comes from the expansion of the macro `impl_from_bool` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0277`.error: could not compile `cast-demo` (bin "cast-demo") due to 1 previous errorThe compiler refuses to build a conversion it cannot guarantee — there is no run-time check to forget, because the code never runs. (as on the exact same expression compiles without a peep and hands you 44.)
TryFrom: the narrowing that reports failure
Section titled “TryFrom: the narrowing that reports failure”Narrowing still has to be possible — it just has to admit it can fail. TryFrom is the same trait with a Result:
fn main() { for n in [200i32, 300i32] { match u8::try_from(n) { Ok(v) => println!("{n} fits in a u8: {v}"), Err(e) => println!("{n} does not: {e}"), } }}200 fits in a u8: 200300 does not: number too large to fit in target typeu8::try_from(n) returns Ok for every value that fits and Err for the ones that do not — the same shape as the parse result in How to convert a String to a number. Calling .unwrap() on it instead would panic on the first value that does not fit, which trades the silent wrong number for a loud crash; handling the Err is the only option that keeps both.
Which one, when
Section titled “Which one, when”- Widening — use
From(or.into()).asis equally lossless here, butFromstates the intent, and if someone later changesi64toi32on one side, the code stops compiling instead of silently truncating. - Narrowing, and failure is a real case —
try_from, handle theErr. This is the default answer for “cast a big number to a small type”. as— only when truncation is exactly what you mean, such as grabbing the low byte of a wider value, and say so in a comment. Everywhere else it converts a type mismatch into a wrong number with no diagnostic at all.
- Working with numbers — the rest of the chapter.