</>
Skip to content
Rust lessons (4/43)

Rust — Syntax

The shape of Rust code

fn main() {
    let greeting = "Hello";
    println!("{}, world!", greeting);
}

Reading the pieces:

PieceMeaning
fndefines a function
mainthe entry point name (fixed)
letdeclares a variable
println!macro call — ! is mandatory for macros
;ends a statement

Statements vs expressions — the core distinction

Rust is expression-oriented. This single idea explains half the language's style.

Statement — performs an action, returns nothing:

let x = 5;          // statement: binding a value

Expression — evaluates to a value:

5 + 3               // expression → 8
"hello"             // expression → that string

Function bodies and blocks return their last expression — without a semicolon:

fn add(a: i32, b: i32) -> i32 {
    a + b            // ← no semicolon = this value is returned
}

Add a semicolon and you've turned it into a statement returning nothing — the compiler then complains about the missing return type:

fn broken(a: i32, b: i32) -> i32 {
    a + b;           // ❌ statement discards the value
}

That stray semicolon error will bite you exactly once. Then it never will again.

Types are annotated after the name

Opposite of Java/C order:

let count: u32 = 10;        // type AFTER variable
let price: f64 = 9.99;

fn area(width: f64, height: f64) -> f64   // arrow for return type

Macros — the exclamation marks

Names ending in ! are macros — code that writes code at compile time:

println!("plain");                 // print with newline
print!("no newline");
println!("{} scored {}", name, score);  // {} are placeholders
println!("{:?}", some_vector);     // Debug formatting for complex types
vec![1, 2, 3]                      // vec! creates vectors

Why macros instead of functions? println! accepts any number of arguments of any printable type — regular Rust functions can't be that flexible.

Shadowing — redeclare, don't just reassign

let again on the same name creates a NEW binding:

let spaces = "   ";      // string slice
let spaces = spaces.len(); // now a number — same name, new binding ✓

This is idiomatic Rust: transform a value and keep the meaningful name, without inventing spaces2. Different from mut — shadowing can even change the type; mut cannot.

Comments

// line comment

/* block
   comment */

/// doc comment — generates docs & shows in editor hover
/// Adds two numbers together.
fn add(a: i32, b: i32) -> i32 { a + b }

Triple-slash doc comments attach to the item below and power cargo doc.

Formatting conventions

  • 4-space indentation (cargo fmt enforces it automatically)
  • snake_case for variables/functions: user_count
  • UpperSnakeCase for constants: MAX_POINTS
  • PascalCase for types: UserProfile

Run cargo fmt after every session — the whole ecosystem matches this style because nobody argues with the formatter.

Mini Practice

  1. Write fn square(x: i32) -> i32; break it by adding a semicolon; read the error.
  2. Print a formatted line with two {} placeholders.
  3. Shadow a string into its length like the example above.
  4. Try //, /* */ and /// comments; run cargo doc --open.
  5. Run cargo fmt on deliberately messy code.

Next: output →

Related Topics

Frequently Asked Questions about Syntax

What is Syntax in Rust?

Syntax is a fundamental concept in Rust. This lesson explains it step by step with clear examples, making it easy for beginners to understand.

How do I learn Syntax?

Start by reading the explanation above, then try the code examples. Practice by modifying the examples and experimenting with different values. Hands-on practice is the best way to learn Syntax.

Why is Syntax important in Rust?

Syntax is essential for Rust development. Understanding this concept will help you write better code and solve real-world problems more effectively.