16.2 Casting with as
The as
keyword is Rust’s simplest way to convert between types. It is often used for numeric conversions, pointer casts, and other low-level operations. While as
is versatile, its behavior is not always intuitive and requires careful attention to potential pitfalls.
16.2.1 Overview of as
The as
keyword works for:
- Primitive Types: Casting between integers, floating-point types, and pointers.
- Enums to Integers: Converting an enum variant into its discriminant value.
- Booleans to Integers: Converting booleans to integers, resulting in
0
forfalse
and1
fortrue
. - Pointers: Casting between raw pointer types, such as
*const T
to*mut T
. - Type Inference:
as
can also be used with the_
placeholder when the destination type can be inferred. Note that this can cause inference breakage and usually such code should use an explicit type for both clarity and stability.
16.2.2 Casting Between Numeric Types
The as
keyword can convert between numeric types, such as i32
to f64
or u16
to u8
. However, as
does not perform runtime checks for overflow or truncation.
When casting between signed and unsigned types, as
interprets the bit pattern of the value without modification. This can lead to surprising results.
Example:
fn main() { let x: u16 = 500; let y: u8 = x as u8; // Truncates to fit within u8 range println!("x: {}, y: {}", x, y); // Outputs: x: 500, y: 244 let x: u8 = 255; let y: i8 = x as i8; // Interpreted as -1 due to two's complement println!("x: {}, y: {}", x, y); // Outputs: x: 255, y: -1 }
16.2.3 Overflow and Precision Loss
When casting from a larger type to a smaller type, as
truncates the value to fit the target type. For floating-point to integer conversions, the fractional part is discarded. Converting from an integer to a floating-point type may lose precision.
Example:
fn main() { let i: i64 = i64::MAX; let x: f64 = i as f64; // Precision loss println!("i: {}, x: {}", i, x); // i: 9223372036854775807, x: 9223372036854776000 let x: f64 = 1e19; let i: i64 = x as i64; // Saturated at i64::MAX println!("x: {}, i: {}", x, i); // x: 10000000000000000000, i: 9223372036854775807 }
16.2.4 Casting Enums to Integer Values
You can cast enum variants to their underlying integer values using as
.
Example:
#[derive(Debug, Copy, Clone)] #[repr(u8)] enum Color { Red = 1, Green = 2, Blue = 3, } fn main() { let color = Color::Green; let value = color as u8; // Cast the enum to its underlying u8 representation println!("The value of {:?} is {}", color, value); // The value of Green is 2 }
Explanation:
- The
#[repr(u8)]
attribute ensures that theColor
enum is represented as au8
in memory. Without this attribute, the default representation may vary. - The
as
keyword casts theColor::Green
variant to its underlying discriminant value (2
in this case).
This approach is commonly used when working with enums that need to interface with external systems or protocols where numeric values are expected.
16.2.5 Performance Considerations
Most as
casts, such as between integers of the same size, enums to integers, or pointer types, are no-ops with no additional performance cost. Truncation during casts to narrower integer types is also highly efficient, typically involving a single instruction.
In contrast, casting between integers and floating-point types (e.g., i32
to f32
or f64
to u32
) incurs a small performance cost due to the need for bit pattern transformations, as these operations are not simple reinterpretations.
16.2.6 Limitations of as
The as
keyword is limited to primitive types and does not work for more complex conversions like those between structs or custom data types. Additionally, as
does not provide error handling, so it may silently produce incorrect results if not used carefully.