Chapter 7: Rust Variables
Rust programming: Variables
In Rust, variables are not like in Python or JavaScript where everything changes freely. Rust makes very deliberate choices about variables to keep your code safe, predictable, and fast. This is where the famous “Rust way” starts to shine (and sometimes frustrate beginners for the first week or two 😄).
Let me teach this like we’re sitting together with VS Code open, going line-by-line, with analogies from everyday life in Hyderabad, lots of examples you can cargo run right now, and clear explanations of why Rust does things this way.
1. Declaring Variables — The let Keyword (No var, no int x =)
Rust uses one main keyword for variables: let
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
fn main() { let name = "Webliance"; // string slice (&str) let age = 25; // integer, inferred as i32 let height = 5.8; // float, inferred as f64 let is_learning_rust = true; // boolean println!("Hello, {}! You are {} years old.", name, age); } |
- No type needed most times → Rust infers it (great productivity!)
- But you can write types explicitly (good for clarity or when compiler can’t guess):
|
0 1 2 3 4 5 6 7 |
let population: u64 = 10_000_000; // underscore = nice separator, like 10,000,000 let pi: f64 = 3.141_592_653_59; |
2. Immutable by Default — The #1 Most Important Rule
In Rust: Variables are immutable unless you say otherwise.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
fn main() { let temperature = 32; // temperature = 35; // ERROR! Cannot assign twice to immutable variable println!("Temp: {}", temperature); } |
Why does Rust force this?
- Safety: Most bugs come from accidentally changing values you didn’t mean to.
- Reasoning: When reading code, if no mut, you know this value never changes → easier to understand big programs.
- Concurrency safety: Later when we do threads, immutable = no data races by default.
Think of it like: Your biryani plate at Paradise → once served, you don’t want someone randomly adding extra mirchi without telling you. Rust prevents that by default.
3. Making Variables Mutable — Add mut
Only when you really need to change it:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
fn main() { let mut score = 100; println!("Initial score: {}", score); score = score + 50; // OK because mut score += 20; // shorthand also works println!("Final score: {}", score); // 170 } |
- mut goes afterlet, before the name
- You can only mutate variables you own (more on ownership soon)
4. Constants — const (Different from immutable variables)
Constants are always immutable, global-ish, and must have type written.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
const MAX_USERS: u32 = 100_000; const PI: f64 = 3.14159; const HYDERABAD_PIN: u32 = 500_081; // example for Madhapur area fn main() { println!("Max users allowed: {}", MAX_USERS); } |
Differences from let:
| Feature | let (immutable) | const |
|---|---|---|
| Keyword | let | const |
| Mutability | Can add mut | Always immutable |
| Type annotation | Optional (inferred) | Required |
| Scope | Block scope | Can be file / crate global |
| Can be computed at runtime? | Yes | No — compile-time only |
| Common use | Normal variables | Magic numbers, config limits |
Use const for things like config values, math constants, limits.
5. Shadowing — Re-declare with let (Very Rust-y Feature!)
Shadowing lets you create a new variable with the same name as an old one.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
fn main() { let city = "Hyderabad"; let city = city.len(); // shadows! now city is usize (length = 9) let city = "Hyderabad, Telangana"; // shadows again, back to &str println!("Final city: {}", city); // "Hyderabad, Telangana" } |
Why is this useful?
- Transform data without inventing names like city_temp, city_str, final_city
- Change type safely (e.g. String → usize → &str)
- Keep code clean — no forced mut just to reuse a name
Shadowing ≠ mutation → Old value still exists until it goes out of scope, but new let hides it.
6. Quick Ownership Teaser (Variables + Memory)
Variables own their data. When variable goes out of scope → data dropped (freed).
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
fn main() { let s1 = String::from("Hello"); // s1 owns the String on heap let s2 = s1; // ownership moved to s2 (move semantics) // println!("{}", s1); // ERROR! s1 no longer valid println!("{}", s2); // Works — s2 owns it now } |
This is why let + no mut is powerful — compiler tracks who owns what.
(We’ll go deep into ownership next if you want!)
Full Small Example to Try Right Now
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
fn main() { // Immutable variable let name = "Webliance"; // Mutable variable let mut temperature = 28; temperature += 4; // Summer heating up in Hyd! // Constant const MAX_TEMP_WARNING: u32 = 40; // Shadowing example let city = String::from("Hyderabad"); println!("City name length: {}", city.len()); let city = city.to_uppercase(); // transform & shadow println!("Uppercase city: {}", city); if temperature > MAX_TEMP_WARNING as f64 { println!("Danger! Too hot!"); } else { println!("Hello {}, {}°C today — nice for Rust coding ☀️", name, temperature); } } |
Run → play with changing values, add mut where needed, shadow types.
Summary in Teacher Voice
Rust Variables =
- Declared with let
- Immutable by default → huge safety + readability win
- mut only when you need change
- const for true constants (compile-time, typed)
- Shadowing lets you reuse names for transformations (no forced mut)
- Everything ties into ownership — variables own data, moves transfer ownership
This is the foundation. Once you love immutable-by-default + shadowing, Rust starts feeling elegant.
Ready for next?
- Deep dive into ownership & borrowing (the real Rust magic)?
- Data types in detail (integers, floats, chars, bool, tuples, arrays)?
- Or functions + how variables work inside them?
Just say the word — your personal Rust teacher is ready! 🦀🚀
