Skip to content

Rust Data Types

Overview

Rust is a statically typed language, and the compiler must know the types of all variables at compile time. This chapter will delve deeply into Rust's type system, including scalar types, compound types, and type conversions.

🔢 Scalar Types

Integer Types

rust
fn integer_types() {
    // Signed integers
    let i8_max: i8 = 127;
    let i8_min: i8 = -128;
    let i16_val: i16 = 32_767;
    let i32_val: i32 = 2_147_483_647;
    let i64_val: i64 = 9_223_372_036_854_775_807;
    let i128_val: i128 = 170_141_183_460_469_231_731_687_303_715_884_105_727;
    let isize_val: isize = 1000; // Platform-dependent size

    println!("Signed integers: {}, {}, {}, {}, {}, {}, {}",
             i8_max, i8_min, i16_val, i32_val, i64_val, i128_val, isize_val);

    // Unsigned integers
    let u8_max: u8 = 255;
    let u16_max: u16 = 65_535;
    let u32_max: u32 = 4_294_967_295;
    let u64_max: u64 = 18_446_744_073_709_551_615;
    let u128_max: u128 = 340_282_366_920_938_463_463_374_607_431_768_211_455;
    let usize_val: usize = 1000; // Platform-dependent size

    println!("Unsigned integers: {}, {}, {}, {}, {}, {}",
             u8_max, u16_max, u32_max, u64_max, u128_max, usize_val);

    // Different representations of integer literals
    let decimal = 98_222;        // Decimal
    let hex = 0xff;              // Hexadecimal
    let octal = 0o77;            // Octal
    let binary = 0b1111_0000;    // Binary
    let byte = b'A';             // Byte literal (u8)

    println!("Different bases: {} {} {} {} {}", decimal, hex, octal, binary, byte);

    // Type suffixes
    let typed_int = 42u32;
    let another = 100_i64;
    println!("Type suffixes: {}, {}", typed_int, another);
}

Floating-Point Types

rust
fn floating_point_types() {
    // Single precision floating-point
    let f32_val: f32 = 3.14159;
    let f32_scientific: f32 = 1.23e-4;

    // Double precision floating-point (default)
    let f64_val: f64 = 2.718281828459045;
    let default_float = 3.14; // Default is f64

    println!("Floating-point numbers: {} {} {} {}", f32_val, f32_scientific, f64_val, default_float);

    // Special floating-point values
    let infinity = f64::INFINITY;
    let neg_infinity = f64::NEG_INFINITY;
    let nan = f64::NAN;

    println!("Special values: {} {} {}", infinity, neg_infinity, nan);

    // Floating-point arithmetic
    let sum = 5.0 + 10.0;
    let difference = 95.5 - 4.3;
    let product = 4.0 * 30.0;
    let quotient = 56.7 / 32.2;
    let remainder = 43.0 % 5.0;

    println!("Arithmetic: {} {} {} {} {}", sum, difference, product, quotient, remainder);

    // Caveats of floating-point comparison
    let a = 0.1 + 0.2;
    let b = 0.3;
    println!("0.1 + 0.2 = {}", a);
    println!("0.3 = {}", b);
    println!("Are they equal? {}", a == b); // May be false

    // Correct floating-point comparison
    let epsilon = f64::EPSILON;
    println!("Approximately equal? {}", (a - b).abs() < epsilon);
}

Boolean Type

rust
fn boolean_type() {
    let t = true;
    let f: bool = false; // Explicit type annotation

    println!("Boolean values: {}, {}", t, f);

    // Boolean operations
    let and_result = t && f;   // Logical AND
    let or_result = t || f;    // Logical OR
    let not_result = !t;       // Logical NOT

    println!("Logical operations: {} {} {}", and_result, or_result, not_result);

    // Short-circuit evaluation
    let result1 = false && expensive_operation(); // expensive_operation won't be called
    let result2 = true || expensive_operation();  // expensive_operation won't be called

    println!("Short-circuit evaluation: {} {}", result1, result2);

    // Boolean to number conversion
    let bool_as_int = t as i32;
    println!("Boolean to number: {}", bool_as_int);
}

fn expensive_operation() -> bool {
    println!("Performing expensive operation");
    true
}

Character Type

rust
fn character_type() {
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '😻';
    let chinese = '中';

    println!("Characters: {} {} {} {}", c, z, heart_eyed_cat, chinese);

    // Different character literal representations
    let newline = '\n';
    let tab = '\t';
    let backslash = '\\';
    let quote = '\'';

    println!("Escape characters: [{}] [{}] [{}] [{}]", newline, tab, backslash, quote);

    // ASCII characters
    let ascii_a = '\x41'; // 'A'
    println!("ASCII: {}", ascii_a);

    // Unicode characters
    let unicode_heart = '\u{2764}'; // ❤
    let unicode_emoji = '\u{1F600}'; // 😀
    println!("Unicode: {} {}", unicode_heart, unicode_emoji);

    // Character-related methods
    println!("Is alphabetic: {}", 'a'.is_alphabetic());
    println!("Is numeric: {}", '5'.is_numeric());
    println!("Is lowercase: {}", 'a'.is_lowercase());
    println!("To uppercase: {}", 'a'.to_uppercase().next().unwrap());
}

📦 Compound Types

Tuple Type

rust
fn tuple_type() {
    // Basic tuple
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    // Destructuring tuple
    let (x, y, z) = tup;
    println!("Destructured tuple: {}, {}, {}", x, y, z);

    // Access by index
    let first = tup.0;
    let second = tup.1;
    let third = tup.2;
    println!("Index access: {}, {}, {}", first, second, third);

    // Nested tuple
    let nested: ((i32, i32), (i32, i32)) = ((1, 2), (3, 4));
    println!("Nested tuple: {:?}", nested);

    // Single-element tuple
    let single_element = (42,); // Note the comma
    let not_tuple = (42);       // This is not a tuple, just parentheses
    println!("Single-element tuple: {:?}", single_element);

    // Empty tuple (unit type)
    let unit = ();
    println!("Unit type: {:?}", unit);

    // Tuple as function return value
    let (sum, product) = calculate(4, 5);
    println!("Calculation result: sum={}, product={}", sum, product);
}

fn calculate(a: i32, b: i32) -> (i32, i32) {
    (a + b, a * b)
}

Array Type

rust
fn array_type() {
    // Basic array
    let arr = [1, 2, 3, 4, 5];
    println!("Array: {:?}", arr);

    // Explicit type and length
    let arr: [i32; 5] = [1, 2, 3, 4, 5];
    println!("Annotated array: {:?}", arr);

    // Same value initialization
    let zeros = [0; 5]; // [0, 0, 0, 0, 0]
    let threes = [3; 4]; // [3, 3, 3, 3]
    println!("Initialized arrays: {:?}, {:?}", zeros, threes);

    // Access array elements
    let first = arr[0];
    let second = arr[1];
    println!("Array elements: {}, {}", first, second);

    // Array length
    println!("Array length: {}", arr.len());

    // Array slicing
    let slice = &arr[1..4]; // Does not include index 4
    println!("Slice: {:?}", slice);

    // Iterate over array
    for element in arr.iter() {
        println!("Element: {}", element);
    }

    // Iterate with index
    for (index, element) in arr.iter().enumerate() {
        println!("Index {}: {}", index, element);
    }

    // Multi-dimensional array
    let matrix: [[i32; 3]; 2] = [[1, 2, 3], [4, 5, 6]];
    println!("Matrix: {:?}", matrix);
    println!("Matrix element: {}", matrix[1][2]); // 6
}

🔄 Type Conversion

Explicit Type Conversion

rust
fn explicit_conversion() {
    // Numeric type conversion
    let integer = 42i32;
    let float = integer as f64;
    let byte = integer as u8;

    println!("Conversion: {} -> {} -> {}", integer, float, byte);

    // Conversion that may lose precision
    let large_number = 1000i32;
    let truncated = large_number as u8; // May truncate
    println!("Truncation: {} -> {}", large_number, truncated);

    // Floating-point to integer conversion
    let pi = 3.14f64;
    let truncated_pi = pi as i32; // Truncates decimal part
    println!("Float to integer: {} -> {}", pi, truncated_pi);

    // Character to number conversion
    let digit_char = '5';
    let digit_value = digit_char as u8; // ASCII value
    println!("ASCII value of character '{}': {}", digit_char, digit_value);

    // Boolean to number conversion
    let true_as_int = true as i32;
    let false_as_int = false as i32;
    println!("Boolean to number: {} -> {}, {} -> {}", true, true_as_int, false, false_as_int);
}

Safe Type Conversion

rust
fn safe_conversion() {
    use std::convert::TryInto;

    // TryInto trait provides safe conversion
    let large_number: i64 = 1000;

    match large_number.try_into() {
        Ok(small_number) => {
            let small: i32 = small_number;
            println!("Safe conversion successful: {} -> {}", large_number, small);
        }
        Err(e) => println!("Conversion failed: {}", e),
    }

    // Conversion that may fail
    let too_large: i64 = i64::MAX;
    match too_large.try_into() {
        Ok(small_number) => {
            let small: i32 = small_number;
            println!("Conversion successful: {}", small);
        }
        Err(e) => println!("Conversion failed: {}", e),
    }

    // String to number parsing
    let number_string = "42";
    match number_string.parse::<i32>() {
        Ok(number) => println!("Parse successful: {} -> {}", number_string, number),
        Err(e) => println!("Parse failed: {}", e),
    }

    let invalid_string = "not_a_number";
    match invalid_string.parse::<i32>() {
        Ok(number) => println!("Parse successful: {}", number),
        Err(e) => println!("Parse failed: {}", e),
    }
}

🏷️ Type Aliases

Defining Type Aliases

rust
// Type aliases
type Kilometers = i32;
type Thunk = Box<dyn Fn() + Send + 'static>;

fn type_aliases() {
    // Using type aliases
    let distance: Kilometers = 100;
    println!("Distance: {} km", distance);

    // Type aliases improve readability
    type UserId = u32;
    type UserName = String;
    type UserAge = u8;

    let id: UserId = 12345;
    let name: UserName = String::from("Alice");
    let age: UserAge = 30;

    println!("User: ID={}, Name={}, Age={}", id, name, age);

    // Aliases for complex types
    type Matrix = Vec<Vec<i32>>;
    type Point3D = (f64, f64, f64);
    type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

    let matrix: Matrix = vec![vec![1, 2], vec![3, 4]];
    let point: Point3D = (1.0, 2.0, 3.0);

    println!("Matrix: {:?}", matrix);
    println!("3D Point: {:?}", point);
}

🔍 Type Inference

Compiler Type Inference

rust
fn type_inference() {
    // Compiler can infer types
    let x = 42;        // i32
    let y = 3.14;      // f64
    let z = true;      // bool
    let s = "hello";   // &str

    println!("Inferred types: {}, {}, {}, {}", x, y, z, s);

    // Inference based on usage
    let mut vec = Vec::new();
    vec.push(1); // Now compiler knows it's Vec<i32>
    println!("Vector: {:?}", vec);

    // Cases requiring type annotation
    let parsed: i32 = "42".parse().expect("Not a number");
    println!("Parse result: {}", parsed);

    // Or use turbofish syntax
    let parsed2 = "42".parse::<i32>().expect("Not a number");
    println!("Turbofish parse: {}", parsed2);

    // Collection type inference
    let numbers = vec![1, 2, 3, 4, 5]; // Vec<i32>
    let words = vec!["hello", "world"]; // Vec<&str>

    println!("Number collection: {:?}", numbers);
    println!("Word collection: {:?}", words);
}

📏 Type Size and Alignment

Viewing Type Sizes

rust
fn type_sizes() {
    use std::mem;

    // Scalar type sizes
    println!("Type sizes (bytes):");
    println!("bool: {}", mem::size_of::<bool>());
    println!("char: {}", mem::size_of::<char>());
    println!("i8: {}", mem::size_of::<i8>());
    println!("i16: {}", mem::size_of::<i16>());
    println!("i32: {}", mem::size_of::<i32>());
    println!("i64: {}", mem::size_of::<i64>());
    println!("i128: {}", mem::size_of::<i128>());
    println!("isize: {}", mem::size_of::<isize>());
    println!("f32: {}", mem::size_of::<f32>());
    println!("f64: {}", mem::size_of::<f64>());

    // Compound type sizes
    println!("(i32, i32): {}", mem::size_of::<(i32, i32)>());
    println!("[i32; 5]: {}", mem::size_of::<[i32; 5]>());
    println!("&str: {}", mem::size_of::<&str>());
    println!("String: {}", mem::size_of::<String>());
    println!("Vec<i32>: {}", mem::size_of::<Vec<i32>>());

    // Alignment information
    println!("\nType alignment:");
    println!("i32 alignment: {}", mem::align_of::<i32>());
    println!("i64 alignment: {}", mem::align_of::<i64>());
    println!("(i8, i64) alignment: {}", mem::align_of::<(i8, i64)>());
}

📝 Chapter Summary

Through this chapter, you should have mastered:

Scalar Types

  • ✅ Range and usage of integer types
  • ✅ Precision and special values of floating-point types
  • ✅ Boolean and character types
  • ✅ Different literal representations

Compound Types

  • ✅ Tuple definition and usage
  • ✅ Array operations and iteration
  • ✅ Multi-dimensional arrays and slices
  • ✅ Type inference mechanism

Type Conversion

  • ✅ Explicit type conversion (as)
  • ✅ Safe type conversion (TryInto)
  • ✅ String parsing and error handling
  • ✅ Type alias definition and usage

Best Practices

  1. Choose appropriate numeric types
  2. Be careful with floating-point comparison
  3. Use safe type conversion
  4. Leverage type inference to simplify code

Continue Learning: Next Chapter - Rust Comments

Content is for learning and research only.