Once your environment is ready, you need a firm grip on variables and data types before writing real code. Rust treats every variable as immutable unless you use mut. Session 3 builds intuition for immutability and walks through scalar types plus composite types such as tuples and arrays.
New terms in this post
- Immutability: The default Rust behavior where variables cannot change after creation unless you explicitly make them mutable.
- Scalar type: A type that represents a single value at a time (
i32,f64,bool,char, and so on). - Tuple: A fixed-length collection that groups values in order, even if their types differ.
- Array: A fixed-length collection that stores values of the same type; the length is part of the type.
Core ideas
- Variables declared with
letare immutable by default; uselet mutwhen you actually need to change them. - Shadowing with
letcreates a brand-new variable rather than modifying the old one. - Rust infers types, but you can annotate with
: Typewhen clarity matters. - Scalar types center on integers (
i32,u64), floats (f32,f64), booleans (bool), and characters (char). - Tuples group multiple values in order; use dot indexing or pattern destructuring.
- Arrays store values of the same type with a fixed length, preparing you for slices later (Session 7).
Code along
1. let and mut
fn main() {
let language = "Rust"; // immutable
let mut version = 1; // mutable
println!("{} v{}", language, version);
version += 1;
println!("Next version: v{}", version);
}
language will trigger a compile error if you try to change it, while version can increase because it uses mut.
2. Explicit scalar types
fn main() {
let signed: i32 = -42;
let unsigned: u64 = 2026;
let pi: f64 = 3.14159;
let ready: bool = true;
let grade: char = 'A';
println!("signed = {signed}, unsigned = {unsigned}");
println!("pi = {pi}, ready = {ready}, grade = {grade}");
}
Literals can carry suffixes (42u8, 1.0f32). Rust defaults to i32 for integers and f64 for floats.
3. Shadowing
fn main() {
let steps = 5;
let steps = steps + 1;
println!("steps = {steps}");
}
The second let steps introduces a new variable that happens to reuse the name. Shadowing is different from mut: shadowing redeclares, while mut modifies the original binding.
4. Tuples
fn main() {
let user: (&str, u32, bool) = ("Jisoo", 3, true);
println!("Name: {}", user.0);
println!("Level: {}", user.1);
println!("Active: {}", user.2);
let (name, level, active) = user; // destructuring
println!("{name} / {level} / {active}");
}
Tuples stay fixed in length and allow mixed types. Get comfortable with dot indexing first, then treat destructuring as an optional convenience.
5. Arrays
fn main() {
let scores = [95, 88, 76, 100];
let zeros = [0; 5]; // length 5, every element is 0
println!("First score: {}", scores[0]);
println!("Array length: {}", scores.len());
for score in scores {
println!("Score: {score}");
}
}
Array length is part of its type, so [u8; 3] and [u8; 4] are different types.
Quick summary:
- Tuple: Can mix types, length is fixed.
- Array: Same type only, length is fixed.
- Both: Compile-time length.
Why it matters
- Immutability reduces bugs from unexpected changes and forces you to mark mutable state intentionally.
- Type clarity helps you read compiler errors and track variable lifetimes when you start learning ownership and borrowing.
- Understanding tuples and arrays makes it easier to connect future topics like structs, slices, and iterators.
Practice in CodeSandbox
The sandbox below uses CodeSandbox's Rust starter. Move the main code into src/main.rs, then compare cargo check and cargo run so you can read the compiler feedback beside the final output.
💬 댓글
이 글에 대한 의견을 남겨주세요