03. Data Types
📋 Jump to Takeaways🎁 How can one language have an integer that's exactly 8 bits wide and another that's 128 bits wide, and refuse to compile if you accidentally mix them up?
Rust is statically typed. Every value has a type known at compile time, and the compiler won't silently convert between them. That strictness is what catches a whole class of bugs before your program ever runs.
Integer Types
Rust provides integers at every power-of-two size, signed and unsigned:
| Signed | Unsigned | Bits | Range (signed) |
|---|---|---|---|
| i8 | u8 | 8 | -128 to 127 |
| i16 | u16 | 16 | -32,768 to 32,767 |
| i32 | u32 | 32 | ≈ -2.1 to 2.1 billion |
| i64 | u64 | 64 | ≈ ±9.2 × 10¹⁸ |
| i128 | u128 | 128 | ≈ ±1.7 × 10³⁸ |
| isize | usize | arch | pointer-sized |
fn main() {
let a: u8 = 255; // max for u8
let b: i16 = -1000; // negative needs signed
let c: usize = 100; // used for indexing
println!("a: {}, b: {}, c: {}", a, b, c);
// Output: a: 255, b: -1000, c: 100
}isize and usize match your CPU architecture (64-bit on modern machines). Use usize for array indices and collection lengths.
Floats, Booleans, and Chars
Beyond integers, you'll reach for floating-point numbers, booleans, and characters constantly:
fn main() {
// Floating point
let f1: f64 = 2.718281828; // double precision (default)
let f2: f32 = 3.14; // single precision
// Boolean
let is_rust: bool = true;
let is_slow: bool = false;
// Character — 4 bytes, supports Unicode
let letter: char = 'A';
let emoji: char = '🦀';
let chinese: char = '中';
println!("{} {} {} {} {} {}", f1, f2, is_rust, is_slow, letter, emoji);
// Output: 2.718281828 3.14 true false A 🦀
println!("char: {}", chinese); // char: 中
}char in Rust is 4 bytes and represents a Unicode scalar value, not just ASCII. This means emoji and international characters are first-class citizens.
Tuples
Need to bundle a few values of different types into one? Tuples group multiple values into one compound value with a fixed length:
fn main() {
let point: (i32, i32) = (10, 20);
let mixed: (i32, f64, bool) = (1, 3.14, true);
// Access by index
println!("x: {}, y: {}", point.0, point.1);
// Output: x: 10, y: 20
// Destructuring
let (a, b, c) = mixed;
println!("a: {}, b: {}, c: {}", a, b, c);
// Output: a: 1, b: 3.14, c: true
}Tuple indices start at 0 and use dot notation. Destructuring lets you unpack all elements into individual variables at once.
Arrays
Arrays hold multiple values of the same type with a fixed length:
fn main() {
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 3]; // [0, 0, 0] — repeat syntax
println!("first: {}", numbers[0]); // first: 1
println!("last: {}", numbers[4]); // last: 5
println!("zeros: {:?}", zeros); // zeros: [0, 0, 0]
println!("length: {}", numbers.len()); // length: 5
}Arrays are stack-allocated and fixed-size. If you access an index out of bounds, Rust panics at runtime rather than allowing undefined behavior. For dynamic-size collections, you'll use Vec<T> later.
String vs &str
Rust has two primary string types. This is a brief introduction, strings get their own deep-dive later.
fn main() {
// &str — string slice, immutable, usually hardcoded
let greeting: &str = "hello";
// String — heap-allocated, growable, owned
let mut name = String::from("Rust");
name.push_str(" language");
println!("{}, {}!", greeting, name);
// Output: hello, Rust language!
println!("length: {}", name.len()); // length: 13
}&str is a borrowed reference to string data, cheap to pass around. String owns its data on the heap and can grow. You'll understand the ownership distinction fully when you reach the ownership lesson.
Key Takeaways
- Integer types:
i8–i128(signed),u8–u128(unsigned), plusisize/usizefor indexing - Rust never converts between numeric types silently, you convert explicitly
- Float types:
f32(single),f64(double, default) boolistrue/false;charis a 4-byte Unicode scalar value- Tuples hold mixed types with a fixed length; arrays hold the same type with a fixed length
- Out-of-bounds array access panics at runtime instead of reading garbage
&stris a borrowed string slice;Stringis an owned, growable string
🎁 Next up: you'll write your own functions and discover that Rust doesn't need a return keyword, the last expression in a function is the return value. This expression-based design changes how you think about code flow entirely.