Rust's Type System: Strict but Smart
Rust is statically typed - every value has a type known at compile time. But thanks to type inference, you rarely need to annotate types explicitly. When you write let x = 5, Rust infers x is an integer. When ambiguous, you annotate.
Integer Types
fn main() {
// Signed integers (i8, i16, i32, i64, i128, isize)
let a: i8 = 127; // -128 to 127
let b: i16 = 32_767; // -32768 to 32767
let c: i32 = 2_147_483_647; // Default integer type
let d: i64 = 9_223_372_036_854_775_807;
let e: i128 = 170_141_183_460_469_231_731_687_303_715_884_105_727;
// Unsigned integers (u8, u16, u32, u64, u128, usize)
let f: u8 = 255; // 0 to 255
let g: u32 = 4_294_967_295;
let h: usize = 1024; // Platform-dependent (pointer size)
// Integer literals
let decimal = 98_222; // Decimal
let hex = 0xff; // Hex
let octal = 0o77; // Octal
let binary = 0b1111_0000; // Binary
let byte = b'A'; // Byte (u8 only)
println!("{} {} {} {} {}", a, b, c, d, h);
}Integer Overflow
In debug mode, integer overflow causes a panic (crash). In release mode, it wraps around silently. Use checked arithmetic (checked_add, saturating_add, wrapping_add) for explicit overflow handling.
Floating Point and Boolean
fn main() {
// Floating point: f32 and f64
let x: f64 = 2.0; // Default float type
let y: f32 = 3.0; // 32-bit (less precise)
// Floating point operations
let sum = x + y as f64; // as for casting
let difference = x - 1.0;
let product = x * 3.0;
let quotient = x / y as f64;
let remainder = x % 3.0;
println!("{} {} {} {} {}", sum, difference, product, quotient, remainder);
// Special float values
let infinity = f64::INFINITY;
let neg_infinity = f64::NEG_INFINITY;
let nan = f64::NAN;
println!("{} {} {}", infinity, neg_infinity, nan);
println!("Is NaN: {}", nan.is_nan());
// Boolean
let t: bool = true;
let f: bool = false;
println!("{} {}", t, f);
println!("AND: {}", t && f);
println!("OR: {}", t || f);
println!("NOT: {}", !t);
}Characters and Text
fn main() {
// char is 4 bytes - a Unicode scalar value
let c: char = 'z';
let emoji: char = '😂';
let heart: char = '❤';
let japanese: char = 'こ';
println!("{} {} {} {}", c, emoji, heart, japanese);
println!("Is alphabetic: {}", c.is_alphabetic());
println!("Is digit: {}", '5'.is_ascii_digit());
println!("To uppercase: {}", 'a'.to_uppercase().next().unwrap());
// char operations
println!("ASCII value of 'A': {}", 'A' as u8); // 65
println!("Char from 66: {}", 66u8 as char); // 'B'
}Compound Types: Tuples and Arrays
fn main() {
// Tuples: fixed-length, heterogeneous
let tup: (i32, f64, bool, char) = (500, 6.4, true, 'z');
// Access by index
println!("First: {}", tup.0);
println!("Second: {}", tup.1);
// Destructuring
let (x, y, flag, letter) = tup;
println!("{} {} {} {}", x, y, flag, letter);
// Unit tuple - empty tuple, used as "void"
let unit: () = ();
// Arrays: fixed-length, homogeneous
let arr: [i32; 5] = [1, 2, 3, 4, 5];
println!("Array: {:?}", arr);
println!("Length: {}", arr.len());
println!("First: {}", arr[0]);
// Initialize with same value
let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
println!("{:?}", zeros);
// Slices of arrays
let slice = &arr[1..3]; // [2, 3]
println!("{:?}", slice);
// Iteration
for element in &arr {
print!("{} ", element);
}
println!();
}Arrays vs Vectors
Arrays in Rust have a fixed size known at compile time and live on the stack. For dynamic collections that can grow, use Vec<T> - covered in Module 5. Use arrays when the size is always known and small.
Type Casting
fn main() {
// Use 'as' for numeric casting
let x: i32 = 1000;
let y = x as u8; // Truncates! 1000 % 256 = 232
println!("{}", y); // 232, not 1000!
let f: f64 = 3.9;
let i = f as i32; // Truncates, does not round
println!("{}", i); // 3
// Safe conversions with From/Into
let a: i32 = 5;
let b: i64 = i64::from(a); // i32 always fits in i64
let c: i64 = a.into(); // Same thing, different syntax
// Fallible conversions with TryFrom/TryInto
use std::convert::TryFrom;
let big: i64 = 1000;
let result = i8::try_from(big); // Returns Result
match result {
Ok(val) => println!("Fits: {}", val),
Err(e) => println!("Does not fit: {}", e),
}
}🎯 Practice Exercise
- Create variables of at least 6 different types and print them
- Create a tuple holding (name: &str, age: u8, height_cm: f32) and destructure it
- Create an array of 5 temperatures (f64), iterate over it, and print the average
- Demonstrate integer overflow by intentionally creating one in debug mode (use
checked_addto handle it safely)
🎉 Key Takeaways
- Integers: signed (i8-i128), unsigned (u8-u128), architecture-sized (isize/usize)
- Floats: f32 (less precise) and f64 (default, use this)
- char is 4 bytes - a full Unicode scalar value, not a single byte
- Tuples: fixed-length, mixed types, accessed by
.0,.1, etc. - Arrays: fixed-length, same type, stack-allocated. For dynamic size use Vec
- Use
asfor numeric casting, but be aware of truncation