Rust Type Conversion (Part 1)

This series details how to how to convert between Rust’s data types and how to navigate some of the edge cases.

Background

Rust is a strongly-typed programming language with static typing. It’s also quite pedantic. For example, it’s impossible to compare two integers with each other if they are of the different types. To avoid any extra work at runtime, such as reflection, Rust requires us to be precise with the types that our programs use.

This small program won’t compile:

// https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4a0ad78e651e74bc52d986037eb21b0c
fn main() {
    let a: i32 = 10;
    let b: u32 = 10;
    println!("{}", a + b);
}

Instead creating a program that prints 20  to the console, we receive an error message with two error codes:

error[E0308]: mismatched types
 --> src/main.rs:4:22
  |
4 |   println!("{}", a + b); 
  |                      ^ expected `i32`, found `u32`

error[E0277]: cannot add `u32` to `i32`
 --> src/main.rs:4:20
  |
4 |   println!("{}", a + b); 
  |                    ^ no implementation for `i32 + u32`
  |
  = help: the trait `Add<u32>` is not implemented for `i32`

For the curious, here is some information about how to interpret those error codes. The first (E0308) is caused because the addition operator (+) expects the same type for both of its operands. The second error (E0277) is subtly different. It’s saying that the trait bounds (the interface) are not satisfied. Addition is provided by the std::ops::Add  trait and has been implicitly parameterized to i32 type by its left operand.

Explicitly changing types with the as keyword

The most common method you’ll encounter to convert one type to another is via the as  keyword. It’s particularly common when converting between usize (which may be 32 or 64 bits wide, depending on CPU architecture) and fixed-width integers, such as u32.

To fix the earlier example, we can choose to cast the type of b from u32 to i32:

// https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=660be23a5e6d16022045fd212a9e763e
fn main() {
    let a: i32 = 10;
    let b: u32 = 10;
    println!("{}", a + b as i32);
}

The as keyword asks Rust to do the minimal amount of work possible to treat a value of one type as a value of another. Very often, such as converting between integer types of equal width, the internal representation does not change. This efficiency comes at a cost. If the value cannot fit within the bounds of the type being converted to — a negative number cannot be represented by an unsigned integer type, for example — then the program will crash.

As is only available for primitive types. When you require conversions of types that you’ve designed yourself, you need to use the std::convert::{From,Into} traits.

The Rust reference provides a good overview of the details in the type cast expression page.


Posts to come in this series

  • Using the From and Into traits
  • Hidden type conversion via the Deref trait
  • Trait objects
  • Downcasting