Web Analytics

Type Conversion in Rust

Beginner ~25 min read

Type conversion is the process of converting a value from one type to another. Rust provides several ways to perform type conversions, ranging from simple casting with the as keyword to safe, trait-based conversions. In this lesson, you'll learn when and how to use each method.

The as Keyword

The as keyword is used for primitive type conversions. It's simple but can be unsafe:

Output
Click Run to execute your code
Warning: The as keyword can truncate values without warning! Converting a large number to a smaller type will silently lose data.

Common Uses of as

Conversion Example Notes
Integer to Integer let y: i64 = x as i64 Can truncate if target is smaller
Float to Integer let i: i32 = f as i32 Truncates decimal part
Integer to Float let f: f64 = i as f64 Safe, no data loss
Char to Integer let n: u32 = c as u32 Gets Unicode code point
Boolean to Integer let n: i32 = b as i32 true=1, false=0
Best Practice: Use as only for primitive types when you're certain the conversion is safe. For more complex conversions, use traits like From and Into.

From and Into Traits

The From and Into traits provide safe, explicit type conversions:

The From Trait

From defines how to create a type from another type:

// Converting &str to String
let my_str = "Hello";
let my_string = String::from(my_str);

// Converting i32 to i64
let small: i32 = 42;
let large: i64 = i64::from(small);

The Into Trait

Into is the reciprocal of From. If a type implements From, it automatically gets Into:

let num: i32 = 42;
let num_i64: i64 = num.into(); // Calls i64::from(num)

let text: String = "Hello".into(); // Calls String::from("Hello")
Key Difference:
  • From::from(value) - Explicit about the target type
  • value.into() - Target type inferred from context
Output
Click Run to execute your code

Parsing Strings to Numbers

The parse method converts strings to numbers. It returns a Result because parsing can fail:

// Using parse with type annotation
let number_str = "42";
let number: i32 = number_str.parse().expect("Not a number!");

// Using turbofish syntax
let float_num = "3.14".parse::().expect("Not a float!");

// Handling errors with match
let input = "not a number";
match input.parse::() {
    Ok(n) => println!("Parsed: {}", n),
    Err(e) => println!("Parse error: {}", e),
}
Turbofish Syntax: The ::<Type> syntax (called "turbofish") explicitly specifies the type parameter when it can't be inferred.

TryFrom and TryInto Traits

For conversions that can fail, use TryFrom and TryInto. They return a Result:

use std::convert::TryFrom;
use std::convert::TryInto;

// TryFrom - safe conversion that can fail
let big_num: i64 = 1000;
let small_num: Result = i8::try_from(big_num);

match small_num {
    Ok(n) => println!("Converted: {}", n),
    Err(e) => println!("Conversion failed: {}", e),
}

// TryInto - the reverse
let value: i32 = 100;
let result: Result = value.try_into();
match result {
    Ok(n) => println!("Success: {}", n),
    Err(e) => println!("Failed: {}", e),
}
Trait Use Case Returns
From Infallible conversions Target type
Into Infallible (reciprocal of From) Target type
TryFrom Fallible conversions Result<T, E>
TryInto Fallible (reciprocal of TryFrom) Result<T, E>
When to Use Which:
  • Use as for simple primitive conversions where you're certain it's safe
  • Use From/Into for infallible conversions
  • Use TryFrom/TryInto when conversion might fail
  • Use parse for string-to-number conversions

Common Type Conversions

String Conversions

// &str to String
let s1 = String::from("Hello");
let s2 = "World".to_string();

// String to &str
let owned = String::from("Hello");
let borrowed: &str = &owned;

// Number to String
let num = 42;
let num_str = num.to_string();
let formatted = format!("{}", num);

Number Conversions

// Safe widening (smaller to larger)
let small: i32 = 42;
let large: i64 = small.into();

// Unsafe narrowing (use TryFrom)
let big: i64 = 1000;
let small: i8 = i8::try_from(big).unwrap_or(-1);

// Float to int (truncates)
let pi: f64 = 3.14;
let pi_int: i32 = pi as i32; // 3

Common Mistakes

1. Silent truncation with as

Problem:

let big: u32 = 1000;
let small: u8 = big as u8; // Silently becomes 232 (1000 % 256)

Solution:

let big: u32 = 1000;
let small: u8 = u8::try_from(big).unwrap_or(255); // Handle overflow

2. Not handling parse errors

Wrong:

let num: i32 = "not a number".parse().unwrap(); // Panics!

Correct:

let num: i32 = "not a number".parse().unwrap_or(0); // Default value
// Or use match to handle error properly

3. Confusing From and Into

Problem:

let s: String = "Hello".from(); // Error: from is not a method!

Correct:

let s = String::from("Hello"); // From is a trait method
let s: String = "Hello".into(); // Into is a method (needs type annotation)

Exercise: Type Conversion Practice

Task: Fix the code to perform type conversions correctly.

Requirements:

  • Parse strings to numbers safely
  • Convert between numeric types
  • Handle conversion errors properly
  • Use appropriate conversion methods
Output
Click Run to execute your code
Show Solution
use std::convert::TryInto;

fn main() {
    // Parse string to integer
    let age_str = "25";
    let age: i32 = age_str.parse().expect("Failed to parse age");
    println!("Age: {}", age);
    
    // Convert float to integer
    let price: f64 = 19.99;
    let price_int = price as i32;
    println!("Price (integer): {}", price_int);
    
    // Safe conversion with error handling
    let big_number: i64 = 1000;
    let small_number: Result = big_number.try_into();
    match small_number {
        Ok(n) => println!("Converted: {}", n),
        Err(_) => println!("Number too large for i8"),
    }
    
    // Parse with error handling
    let input = "not a number";
    match input.parse::() {
        Ok(n) => println!("Parsed: {}", n),
        Err(e) => println!("Parse error: {}", e),
    }
    
    // Char to Unicode
    let letter = 'Z';
    let unicode_value = letter as u32;
    println!("Unicode value of '{}': {}", letter, unicode_value);
    
    // &str to String
    let str_slice = "Hello, Rust!";
    let owned_string = String::from(str_slice);
    println!("String: {}", owned_string);
    
    // Boolean to integer
    let is_active = true;
    let status_code = is_active as i32;
    println!("Status code: {}", status_code);
}

Summary

  • as keyword: Simple but unsafe primitive conversions, can truncate
  • From trait: Infallible conversions, explicit target type
  • Into trait: Reciprocal of From, target type inferred
  • parse method: Convert strings to numbers, returns Result
  • TryFrom/TryInto: Safe fallible conversions, return Result
  • Always handle errors when parsing or converting between incompatible sizes
  • Use unwrap_or or match to handle conversion failures
  • Prefer trait-based conversions over as for safety

What's Next?

Now that you understand type conversion, you're ready to learn about functions. In the next lesson, you'll learn how to define functions, pass parameters, return values, and understand the crucial difference between statements and expressions in Rust.