๐ท๏ธ Rust Type System Basics: Your Safety Labels
Imagine youโre organizing a toy box. Every toy has a label: โcarโ, โdollโ, โblockโ. Rust does the same with your data!
๐ฏ The Big Picture
In Rust, every piece of data has a type. Types are like name tags that tell Rust:
- How much space to save
- What you can do with it
- How to keep it safe
Think of types as safety labels on food containers. A label saying โmilkโ tells you it goes in the fridge, not the pantry!
graph TD A["Your Data"] --> B{What Type?} B --> C["Number?"] B --> D["Text?"] B --> E["True/False?"] C --> F["i32, u64, f32..."] D --> G["String, &str..."] E --> H["bool"]
๐ Type Annotations: Writing Your Own Labels
Sometimes Rust is smart enough to guess the type. But sometimes YOU need to tell it!
The Syntax
let age: i32 = 10;
// ^^^^
// This is your label!
When Do You Need Annotations?
| Situation | Example |
|---|---|
| Rust canโt guess | let x: i32 = "5".parse().unwrap(); |
| You want a specific type | let small: i8 = 5; |
| Function parameters | fn greet(name: &str) |
๐ Real Example
// Without annotation - Rust guesses i32
let apples = 5;
// With annotation - YOU choose!
let oranges: u8 = 5;
let bananas: i64 = 5;
Why different types?
u8= tiny box (0 to 255)i32= medium box (-2 billion to +2 billion)i64= huge box (even bigger numbers!)
๐ Type Casting with as: Changing Labels
What if you have a u8 but need an i32? You cast it!
The Magic Word: as
let small: u8 = 42;
let big: i32 = small as i32;
// ^^^^^^
// The magic!
โ ๏ธ Be Careful!
Casting can lose data:
let big: i32 = 300;
let tiny: u8 = big as u8;
// tiny = 44 (not 300!)
// Why? 300 is too big for u8!
graph TD A["300 as i32"] -->|Cast to u8| B["44"] C["Overflow!"] --> D["300 - 256 = 44"]
๐ฏ Safe Casting Rules
| From | To | Safe? |
|---|---|---|
u8 |
i32 |
โ Always |
i32 |
u8 |
โ ๏ธ Can overflow |
f64 |
i32 |
โ ๏ธ Loses decimals |
i32 |
f64 |
โ Always |
๐ฅ Integer Overflow: When Numbers Get Too Big
Imagine pouring 2 liters of water into a 1-liter bottle. What happens? OVERFLOW!
What Rust Does
In Debug Mode (while developing):
let max: u8 = 255;
let overflow = max + 1;
// PANIC! Program crashes to warn you!
In Release Mode (final app):
let max: u8 = 255;
let wrapped = max + 1;
// wrapped = 0 (wraps around!)
๐ ๏ธ Handling Overflow Safely
Rust gives you special methods:
let x: u8 = 250;
// Wrapping - goes around like a clock
let a = x.wrapping_add(10); // = 4
// Checked - returns None if overflow
let b = x.checked_add(10); // = None
// Saturating - stops at max
let c = x.saturating_add(10); // = 255
// Overflowing - tells you if it wrapped
let (d, did_wrap) = x.overflowing_add(10);
// d = 4, did_wrap = true
graph TD A["250 + 10"] --> B{Which Method?} B -->|wrapping| C["4 - wrapped around"] B -->|checked| D["None - detected!"] B -->|saturating| E["255 - max value"] B -->|overflowing| F["4 + true flag"]
๐ฎ Type Inference: Rustโs Mind Reading
Rust is SMART. It can often guess types without you telling it!
How It Works
let x = 5; // Rust sees: "that's an i32"
let y = 3.14; // Rust sees: "that's an f64"
let z = true; // Rust sees: "that's a bool"
let s = "hello"; // Rust sees: "that's a &str"
Inference from Usage
let mut numbers = Vec::new();
// Rust: "Hmm, what type goes in this Vec?"
numbers.push(42);
// Rust: "Aha! It's Vec<i32>!"
When Inference Fails
// โ This won't work
let guess = "42".parse().unwrap();
// Error: Rust can't guess!
// โ
This works
let guess: i32 = "42".parse().unwrap();
// Now Rust knows what to parse into!
๐ Turbofish Syntax: ::<Type>
The turbofish looks like a fish: ::<>
It helps when Rust canโt infer the type for a function or method.
The Shape
function_name::<Type>(arguments)
// ^^^^^^^
// The turbofish!
Real Examples
// Parsing a string to a number
let num = "42".parse::<i32>().unwrap();
// ^^^^^^^
// Turbofish!
// Creating a vector with a specific type
let v = Vec::<u8>::new();
// Collecting into a specific type
let nums: Vec<i32> = (0..5).collect();
// OR using turbofish:
let nums = (0..5).collect::<Vec<i32>>();
๐ When to Use Turbofish
| Situation | Example |
|---|---|
.parse() calls |
"5".parse::<i32>() |
.collect() calls |
.collect::<Vec<_>>() |
| Generic functions | std::mem::size_of::<u32>() |
Pro Tip: The Underscore
When Rust knows PART of the type:
// Rust knows it's a Vec, just not what's inside
let nums = (0..5).collect::<Vec<_>>();
// ^
// "You figure it out, Rust!"
๐ฎ Quick Reference
| Concept | Syntax | Example |
|---|---|---|
| Type Annotation | let x: Type |
let x: i32 = 5; |
| Type Casting | value as Type |
x as f64 |
| Turbofish | ::<Type> |
.parse::<i32>() |
| Checked Add | .checked_add() |
x.checked_add(5) |
| Wrapping Add | .wrapping_add() |
x.wrapping_add(5) |
| Saturating Add | .saturating_add() |
x.saturating_add(5) |
๐ You Did It!
Now you understand:
- โ How to write type annotations
- โ How to safely cast between types
- โ How to handle integer overflow
- โ How Rust infers types automatically
- โ
When to use the turbofish
::<>
Remember: Types are your friends! They catch bugs before they happen. ๐ฆ
