beginner30 minutesLesson 4 of 10

Data Types

Master Rust's scalar types, compound types, type inference, and explicit type annotations

Data Types

Rust is a statically typed language — every variable must have a known type at compile time. The compiler is smart about inferring types, but you can always annotate them explicitly.

Two Categories of Types

CategoryDescriptionExamples
ScalarSingle valueintegers, floats, bool, char
CompoundGroup of valuestuples, arrays, structs, enums

Scalar Types

Integer Types

Rust offers signed (i) and unsigned (u) integers in various sizes:

SizeSignedUnsignedRange (signed)
8-biti8u8-128 to 127
16-biti16u16-32,768 to 32,767
32-biti32u32-2³¹ to 2³¹ -1
64-biti64u64-2⁶³ to 2⁶³ -1
128-biti128u128-2¹²⁷ to 2¹²⁷ -1
archisizeusizedepends on platform (32/64-bit)
rust
fn main() { let a = 42; // i32 (default) let b: u8 = 255; // Explicit unsigned byte let c = 100_000; // Underscore separators for readability let d = 0xff; // Hexadecimal let e = 0o77; // Octal let f = 0b1111; // Binary let g = b'A'; // Byte literal (u8, ASCII) // usize/isize for indexing collections let arr = [1, 2, 3]; let index: usize = 1; println!("{0}", &arr[index]); // 2 }
ℹ️Note

i32 is the default integer type because it's fast on modern CPUs and avoids overflow issues common with smaller types.

Integer Overflow

rust
fn main() { let mut x: u8 = 255; // x = x + 1; // PANICS in debug mode (overflow check) // Explicit wrapping: let y = x.wrapping_add(1); // 0 (wraps around) let z = x.saturating_add(1); // 255 (saturates at max) println!("wrapping: {y}, saturating: {z}"); }
MethodBehavior
wrapping_addWraps around (two's complement)
saturating_addStops at min/max value
overflowing_addReturns (result, overflowed bool)
checked_addReturns Option (None on overflow)
⚠️Warning

In debug mode, integer overflow panics. In release mode, it wraps silently. Never rely on overflow behavior — use explicit methods.

Floating-Point Types

rust
fn main() { let x = 2.0; // f64 (default, double precision) let y: f32 = 3.0; // f32 (single precision) // f64 operations let squared = x.powi(2); // 4.0 let sqrt = x.sqrt(); // 1.414... let remainder = 5.0 % 2.0; // 1.0 }
TypePrecisionSizeUse Case
f32~7 decimal digits4 bytesGraphics, GPUs
f64~15 decimal digits8 bytesGeneral computation
⚠️Warning

Floats do not implement Eq or Ord — NaN and precision issues make comparison unreliable. Use f64::EPSILON for approximate comparison:

rust
fn approx_eq(a: f64, b: f64) -> bool { (a - b).abs() < f64::EPSILON }

The Boolean Type

rust
fn main() { let is_rust_fun = true; let is_hard: bool = false; if is_rust_fun { println!("Rust is fun!"); } // Conversion to integer println!("{}", true as u8); // 1 println!("{}", false as u8); // 0 }

The Character Type

char is 4 bytes and represents a Unicode Scalar Value:

rust
fn main() { let c = 'z'; let z: char = 'ℤ'; let heart_eyed_cat = '😻'; println!("{c} {z} {heart_eyed_cat}"); // char as number println!("{}", 'A' as u8); // 65 println!("{}", '😻' as u32); // 128571 }
ℹ️Note

char is 4 bytes (not 1 like C char). It supports full Unicode but is not ASCII — use u8 or &[u8] for byte-level data.

Compound Types

Tuples

Tuples group values of different types. Fixed length, known at compile time.

rust
fn main() { let tup: (i32, f64, char) = (500, 6.4, 'x'); // Destructuring let (x, y, z) = tup; println!("{x}, {y}, {z}"); // Dot notation (0-indexed) println!("{}", tup.0); // 500 println!("{}", tup.1); // 6.4 // Unit tuple (empty tuple) let unit: () = (); }
PatternExampleWhen to Use
Destructurelet (a, b) = tupExtract all values
Dot accesstup.0Extract one value
Ignorelet (a, _, _) = tupExtract some values

Arrays

Arrays are fixed length, all elements same type, stored on stack:

rust
fn main() { let arr = [1, 2, 3, 4, 5]; // Type annotation: [type; length] let typed: [i32; 5] = [1, 2, 3, 4, 5]; // Repeat expression: [value; count] let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // Access let first = arr[0]; let second = arr[1]; // Bounds-checked at runtime // let oops = arr[10]; // Panics: index out of bounds }
FeatureArrayTuple
Same type?YesNo
Fixed length?YesYes
Default type[T; N](T1, T2, ...)
Accessarr[i] (runtime-checked)tup.i (compile-time)

Vectors (Preview)

Vectors are heap-allocated, growable arrays:

rust
fn main() { let mut vec = vec![1, 2, 3]; vec.push(4); // Now [1, 2, 3, 4] println!("{vec:?}"); }

Type Inference and Annotations

Rust infers types in most situations:

rust
fn main() { // Inference works let x = 42; // i32 let y = 3.14; // f64 let cond = true; // bool // Annotations clarify intent let port: u16 = 8080; let pi: f32 = 3.14159; // Sometimes required (type ambiguity) let ambiguous = "hello".parse(); // ERROR: can't infer type let parsed: u32 = "42".parse().unwrap(); // OK with annotation // Turbofish syntax let n = "100".parse::<i32>().unwrap(); }
Success
| Situation | Example | |-----------|---------| | `parse()` needs annotation | `"42".parse::()` or `let x: i32 = "42".parse()?` | | Integer default | `let x = 42` → `i32` | | Float default | `let x = 3.14` → `f64` | | Collection generic | `Vec::new()` needs type context |

Type Aliases

rust
type Kilometers = i32; type Thunk = Box<dyn FnOnce() + Send>; fn main() { let distance: Kilometers = 100; println!("{distance} km"); }

Sized and Unsized Types

CategoryExamplesSize at Compile Time?
Sizedi32, f64, [i32; 5], StringYes
Unsizedstr, [i32], dyn TraitNo (behind pointer)

Unsized types must always be behind a pointer: &str, Box<dyn Trait>.

Practice Questions

  1. What is the default integer type in Rust? What about float?
  2. How many bytes is a char in Rust? How is this different from C?
  3. What's the difference between a tuple and an array?
  4. Why can't you compare two f64 values with == directly?
  5. What happens if you access an array index out of bounds?
  6. When must you annotate a variable's type explicitly?
  7. What is the unit type () used for?
  8. How do you parse a string into an integer?
  9. What's the difference between usize and u64?
  10. What methods can you use to handle integer overflow safely?
Progress40%