Rust 03. Variables, Types, Control Flow, and Functions
Summary
When you first learn Rust, the first syntax group that really matters is variables, types, control flow, and functions. Once those four are clear, later topics such as struct, enum, ownership, and borrowing become much easier to follow.
This post keeps everything inside one Cargo project and walks through default immutability, commonly used types, if/loop/while/for/match, and function parameters and return values. The practical idea is simple: learn how values are stored, typed, branched, repeated, and grouped into functions as one connected flow.
Document Information
- Written on: 2026-04-09
- Verification date: 2026-04-15
- Document type: tutorial
- Test environment: Cargo project, Windows PowerShell example commands,
src/main.rs - Test version: rustc 1.94.0, cargo 1.94.0
- Source quality: only official documentation is used.
- Note: example outputs and diagnostics are shown to explain structure. Exact wording can vary across Rust versions.
Problem Definition
At the beginner stage, the following four topics are easy to understand separately but harder to connect.
- the difference between
let,let mut, and shadowing - the role of common types such as numbers, strings, arrays, and tuples
- when each branching and loop form feels natural
- how function parameters, return types, and expressions work together
This post focuses on connecting those ideas inside one Cargo project you can run immediately. It does not cover struct, enum, Result, Option, or advanced pattern matching.
Verified Facts
- Rust variables are immutable by default, and
mutand shadowing are different concepts. Evidence: Variables and Mutability - Rust data types are grouped into scalar and compound types, and
parse()is a common case where an explicit type annotation is needed. Evidence: Data Types ifrequires abool,loopcan return a value throughbreak, andwhileandforare used for different repetition patterns. Evidence: Control Flow- Rust functions are defined with
fn, and if the last expression has no semicolon, that value becomes the return value. Evidence: How Functions Work - The beginner practice flow is built around
cargo new. Evidence: Hello, Cargo!
Directly Confirmed Results
1. One Cargo project made the examples easiest to repeat
- Direct result: creating a small Cargo project and replacing
src/main.rswhile rerunningcargo runwas the cleanest learning loop.
cargo new rust-basic-syntax
cd rust-basic-syntax
code .
cargo run
2. Variables were easiest to understand as immutable, mutable, and shadowed bindings
- Direct result: the example below showed the difference between an immutable binding, a mutable binding, and shadowing in one place.
fn main() {
let count = 10;
println!("count = {}", count);
let mut level = 1;
level = level + 1;
println!("level = {}", level);
let spaces = " ";
let spaces = spaces.len();
println!("spaces length = {}", spaces);
}
- Observed result:
count = 10
level = 2
spaces length = 3
- Direct result: reassigning an immutable variable produced a compiler error like the one below.
fn main() {
let count = 10;
count = 20;
}
error[E0384]: cannot assign twice to immutable variable `count`
3. Types were easier to learn through the most common examples first
- Direct result: the following example covered the beginner-level types that come up most often:
i32,f64,bool,char,&str, andString.
fn main() {
let age: i32 = 29;
let temperature: f64 = 36.5;
let is_rust_fun: bool = true;
let grade: char = 'A';
let language: &str = "Rust";
let message: String = String::from("hello");
println!("age = {}", age);
println!("temperature = {}", temperature);
println!("is_rust_fun = {}", is_rust_fun);
println!("grade = {}", grade);
println!("language = {}", language);
println!("message = {}", message);
}
- Observed result:
age = 29
temperature = 36.5
is_rust_fun = true
grade = A
language = Rust
message = hello
- Direct result:
parse()was easiest to understand when the target type was written explicitly.
fn main() {
let guess: i32 = "42".parse().expect("A number is required.");
println!("guess = {}", guess);
}
guess = 42
4. Control flow forms had slightly different roles
- Direct result:
ifrequired aboolcondition and worked well for a simple branch like this.
fn main() {
let number = 7;
if number % 2 == 0 {
println!("It is even.");
} else {
println!("It is odd.");
}
}
It is odd.
- Direct result:
loopcould return a value throughbreak.
fn main() {
let mut count = 0;
let result = loop {
count += 1;
if count == 3 {
break count * 10;
}
};
println!("result = {}", result);
}
result = 30
- Direct result:
whileworked naturally for condition-based repetition,forfor arrays and ranges, andmatchfor value-based branching.
fn main() {
let tools = ["rustc", "cargo", "clippy"];
for tool in tools {
println!("tool = {}", tool);
}
let score = 85;
let grade = match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
_ => "D",
};
println!("grade = {}", grade);
}
tool = rustc
tool = cargo
tool = clippy
grade = B
5. Functions made more sense when parameters, return types, and expressions were shown together
- Direct result: the example below made function parameters, explicit return types, and implicit final-expression returns easy to see together.
fn print_user(name: &str, age: u32) {
println!("name = {}, age = {}", name, age);
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn max(a: i32, b: i32) -> i32 {
if a > b {
a
} else {
b
}
}
fn main() {
print_user("K4NUL", 30);
let sum = add(10, 20);
let bigger = max(7, 11);
println!("sum = {}", sum);
println!("bigger = {}", bigger);
}
- Observed result:
name = K4NUL, age = 30
sum = 30
bigger = 11
- Direct result: adding a semicolon to the final expression of a returning function changed the flow into
()and no longer matched the intended return type.
6. The combined example made the connections clearer
- Direct result: once variables, types,
if,for,match, and functions were placed in one file, the beginner grammar started to feel like one connected system instead of separate rules.
fn describe_score(score: i32) -> &'static str {
match score {
90..=100 => "excellent",
80..=89 => "good",
70..=79 => "not bad",
_ => "keep practicing",
}
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let user = "rust beginner";
let mut score = 70;
score = score + 15;
let level = if score >= 80 { "intermediate" } else { "starter" };
let point: (i32, i32) = (10, 20);
let numbers: [i32; 3] = [1, 2, 3];
println!("user = {}", user);
println!("score = {}", score);
println!("level = {}", level);
println!("point = ({}, {})", point.0, point.1);
for number in numbers {
println!("number = {}", number);
}
let total = add(10, 20);
println!("total = {}", total);
println!("description = {}", describe_score(score));
}
Interpretation / Opinion
- Opinion: for beginners, variables, types, control flow, and functions are easier to learn by rerunning one
main.rsfile than by memorizing each rule separately. - Opinion: it is more practical to learn the most common types first than to try to memorize the full type list all at once.
- Interpretation: the most important outcome at this stage is not “knowing a lot of syntax,” but building a feel for storing values, branching, repeating, and grouping code into functions.
Limits and Exceptions
- This post only covers the most basic grammar inside a Cargo project.
struct,enum,Result,Option, and iterator-heavy patterns are outside the scope. - Exact diagnostics and some inferred behavior can vary across Rust versions.
- This post does not cover macOS, Linux, or WSL-specific differences.
matchis a much deeper topic, but here it is only used as an entry-level branching example.
댓글남기기