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
- Choose appropriate numeric types
- Be careful with floating-point comparison
- Use safe type conversion
- Leverage type inference to simplify code
Continue Learning: Next Chapter - Rust Comments