Skip to content
Select theme

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.

300 is bigger than a u8 can hold (0–255), and as casts it anyway:

300i32 as u8 = 44
-1i32 as u8 = 255

as 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.)

Float-to-integer casts truncate toward zero, and — instead of wrapping — clamp to the target type’s range:

2.9 as i32 = 2
1000.0 as u8 = 255
-2.7 as i32 = -2

The 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 is implemented for exactly the conversions that always succeed — the widening ones:

42 200

Every 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:

Terminal window
cargo run -q
error[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 error

The 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:

200 fits in a u8: 200
300 does not: number too large to fit in target type

u8::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.

  • Widening — use From (or .into()). as is equally lossless here, but From states the intent, and if someone later changes i64 to i32 on one side, the code stops compiling instead of silently truncating.
  • Narrowing, and failure is a real case — try_from, handle the Err. 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.