16.2 Primitive Casting with as

The as keyword provides a direct mechanism for casting between compatible primitive types. It is syntactically similar to C’s (new_type)value but with more restrictions and different behavior in some cases (e.g., saturation on float-to-int overflow). Crucially, as performs no runtime checks for validity beyond basic type compatibility rules enforced at compile time. Using as signifies that the programmer assumes responsibility for the conversion’s correctness and consequences.

16.2.1 Valid as Casts

Common uses of as include:

  • Numeric Casts: Between integer types (i32 as u64, u16 as u8) and between integer and floating-point types (i32 as f64, f32 as u8).
  • Pointer Casts: Between raw pointer types (*const T as *mut U, *const T as usize). These are primarily used within unsafe blocks, often for FFI or low-level memory manipulation.
  • Enum to Integer: Casting C-like enums (those without associated data, potentially with a #[repr(...)] attribute) to their underlying integer discriminant value.
  • Boolean to Integer: bool as integer type (true becomes 1, false becomes 0).
  • Character to Integer: char as integer type (yields the Unicode scalar value).
  • Function Pointers: Casting function pointers to raw pointers or integers, and vice-versa (requires unsafe).

16.2.2 Numeric Casting Behavior with as

Numeric casts using as are common but require caution due to potential value changes:

  • Truncation: Casting to a smaller integer type silently drops the most significant bits. (u16 as u8)
  • Sign Change: Casting between signed and unsigned integers of the same size reinterprets the bit pattern according to two’s complement representation. (u8 as i8)
  • Floating-point to Integer: The fractional part is truncated (rounded towards zero). Values exceeding the target integer’s range saturate (clamp) at the minimum or maximum value of the target type. This saturation behavior differs from C, where overflow during float-to-int conversion often results in undefined behavior.
  • Integer to Floating-point: May lose precision if the integer’s magnitude is too large to be represented exactly by the floating-point type (e.g., large i64 to f64).
fn main() {
    let x: u16 = 500; // Binary 0000_0001 1111_0100
    let y: u8 = x as u8;  // Truncates to 1111_0100 (decimal 244)
    println!("u16 {} as u8 is {}", x, y); // Output: u16 500 as u8 is 244

    let a: u8 = 255; // Binary 1111_1111
    let b: i8 = a as i8;  // Reinterpreted as two's complement: -1
    println!("u8 {} as i8 is {}", a, b); // Output: u8 255 as i8 is -1

    let large_float: f64 = 1e40; // Larger than i32::MAX
    let int_val: i32 = large_float as i32; // Saturates to i32::MAX
    println!("f64 {} as i32 is {}", large_float, int_val);
    // Output: f64 1e40 as i32 is 2147483647

    let small_float: f64 = -1e40; // Smaller than i32::MIN
    let int_val_neg: i32 = small_float as i32; // Saturates to i32::MIN
    println!("f64 {} as i32 is {}", small_float, int_val_neg);
    // Output: f64 -1e40 as i32 is -2147483648

    let precise_int: i64 = 9007199254740993;
    // 2^53 + 1, cannot be precisely represented by f64
    let float_val: f64 = precise_int as f64; // Loses precision
    println!("i64 {} as f64 is {}", precise_int, float_val);
    // Output: i64 9007199254740993 as f64 is 9007199254740992.0
}

16.2.3 Enum and Boolean Casting

Enums without associated data can be cast to integers. Specifying #[repr(integer_type)] ensures a predictable underlying type.

#[derive(Debug, Copy, Clone)]
#[repr(u8)] // Explicitly use u8 for representation
enum Status {
    Pending = 0,
    Processing = 1,
    Completed = 2,
    Failed = 3,
}

fn main() {
    let current_status = Status::Processing;
    let status_code = current_status as u8;
    println!("Status {:?} has code {}", current_status, status_code);
    // Output: Status Processing has code 1

    let is_active = true;
    let active_flag = is_active as u8; // true becomes 1
    println!("Boolean {} as u8 is {}", is_active, active_flag);
    // Output: Boolean true as u8 is 1
}

16.2.4 When to Use as

Use as primarily when:

  • Performing simple numeric conversions where truncation, saturation, or precision loss is understood and acceptable within the program’s logic.
  • Conducting low-level pointer manipulations or integer-pointer conversions within unsafe blocks.
  • Converting C-like enums or booleans to their integer representations.

Warning: Avoid as for numeric conversions where potential overflow or truncation represents an error condition that should be handled explicitly. Prefer TryFrom/TryInto or checked arithmetic methods in such scenarios.

16.2.5 Performance of as

Numeric casts using as are generally highly efficient, often compiling down to a single machine instruction or even being a no-op (e.g., casting between signed and unsigned integers of the same size like u32 to i32).