RustRust Syntax

Rust Syntax Overview

Rust's syntax is influenced by C and ML-family languages. It looks familiar at a glance but has several distinctive rules — especially around expressions, semicolons, and the type system — that are worth understanding early. This page gives you the bird's-eye view before diving into individual topics.

Statements vs expressions

Rust is an expression-based language. Almost everything produces a value. The distinction between statements and expressions is fundamental:

Produces a value?

Ends with semicolon?

Example

Expression

Yes

No (usually)

5 + 3, if x { 1 } else { 2 }, { let y = 2; y }

Statement

No

Yes

let x = 5;, println!("hi");

RUST
fn main() {
    // Statement — does not produce a value
    let x = 5;

    // Expression — produces a value (used as the argument to println!)
    println!("{}", x + 3);

    // A block is an expression; its value is the last expression inside
    let y = {
        let a = 10;
        let b = 20;
        a + b      // no semicolon — this is the value of the block
    };
    println!("{}", y); // 30
}
Semicolons — with vs without

The presence or absence of a semicolon changes meaning in Rust. This is one of the most common sources of beginner confusion.

RUST
fn add(a: i32, b: i32) -> i32 {
    a + b      // NO semicolon — this expression is the return value
}

fn greet() -> &'static str {
    "hello"    // NO semicolon — returns the string slice
}

fn print_sum(a: i32, b: i32) {
    println!("{}", a + b);  // semicolon — discard the () return value of println!
}

fn broken() -> i32 {
    5;         // semicolon turns this into a statement — returns () not i32
               // This would be a COMPILE ERROR because the return type is i32
}
Warning
Adding a semicolon to the last line of a function changes its return type from the expression's type to `()` (unit). If your function signature declares a return type and you accidentally add a semicolon, Rust will catch it with a type error.
Blocks as expressions

Any block surrounded by curly braces is an expression. This means you can use if, match, loop, and plain blocks on the right-hand side of let.

RUST
fn main() {
    let number = 7;

    // if is an expression — both arms must have the same type
    let description = if number % 2 == 0 {
        "even"
    } else {
        "odd"
    };
    println!("{} is {}", number, description);

    // A plain block as an expression
    let result = {
        let x = 3;
        let y = 4;
        x * x + y * y   // Pythagorean — no semicolon, so this is the value
    };
    println!("Hypotenuse squared: {}", result); // 25
}
Naming conventions — snake_case

Rust enforces naming conventions through compiler warnings. Follow them and your code will feel natural to every Rust developer.

Item

Convention

Example

Variables

snake_case

user_name, is_active

Functions

snake_case

parse_input, get_user

Modules

snake_case

http_client, data_store

Types / Structs

PascalCase

HttpClient, UserAccount

Enums

PascalCase

Direction, Color

Enum variants

PascalCase

Direction::North, Color::Red

Constants

SCREAMING_SNAKE_CASE

MAX_SIZE, PI

Traits

PascalCase

Display, Iterator, Clone

Lifetimes

short lowercase

'a, 'b, 'static

Type annotations

Rust can infer types in most situations, but you can always write them explicitly. Annotations are required when the compiler cannot infer the type — for example, when parsing a string into a number.

RUST
fn main() {
    // Inferred types — compiler figures these out
    let x = 42;          // i32
    let y = 3.14;        // f64
    let name = "Alice";  // &str
    let active = true;   // bool

    // Explicit type annotations — same as above, written out
    let x: i32 = 42;
    let y: f64 = 3.14;
    let name: &str = "Alice";
    let active: bool = true;

    // Required annotation — the compiler cannot know which integer type to parse
    let number: u64 = "12345".parse().unwrap();

    // Annotation on a collection's element type
    let scores: Vec<i32> = Vec::new();
}
Function signatures

Function parameters always require explicit type annotations. The return type follows the -&gt; arrow. If a function returns nothing, the return type is () (unit) and is usually omitted.

RUST
// No return value (returns unit — () implicitly)
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

// Returns an i32
fn add(a: i32, b: i32) -> i32 {
    a + b
}

// Returns a boolean
fn is_even(n: i32) -> bool {
    n % 2 == 0
}

// Multiple parameters, returns a String
fn repeat(text: &str, times: usize) -> String {
    text.repeat(times)
}

// Generic function — works with any type T that implements Display
use std::fmt::Display;
fn print_value<T: Display>(value: T) {
    println!("{}", value);
}
Return values — last expression without semicolon

The idiomatic Rust way to return a value from a function is to write the expression as the last line with no semicolon. The return keyword is available but is reserved for early returns.

RUST
fn square(x: i32) -> i32 {
    x * x          // idiomatic — last expression, no semicolon
}

fn abs_value(x: i32) -> i32 {
    if x < 0 {
        return -x; // early return with the return keyword
    }
    x              // normal return for the positive case
}

fn classify(n: i32) -> &'static str {
    match n {
        i32::MIN..=-1 => "negative",
        0 => "zero",
        1..=i32::MAX => "positive",
        _ => unreachable!(),
    }
    // match is an expression — its value is returned
}
Comments

Rust supports line comments, block comments, and documentation comments. Documentation comments use markdown and can contain runnable code examples.

RUST
// This is a line comment

/* This is a
   block comment */

/// This is a doc comment for the item below it (function, struct, etc.)
/// It supports **markdown** and `inline code`.
fn my_function() {}

//! This is a doc comment for the enclosing module or crate (at the top of a file)
Attributes

Attributes annotate items with metadata. They use the #[...] syntax and are placed directly above the item they modify. Crate-level attributes use #![...] with a bang.

RUST
// Item-level attribute — applies to the next item
#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}

// Suppress a specific compiler warning for one function
#[allow(dead_code)]
fn unused_function() {}

// Mark a function as a test
#[test]
fn test_addition() {
    assert_eq!(2 + 2, 4);
}

// Conditional compilation — only compile on Linux
#[cfg(target_os = "linux")]
fn linux_only() {}

// Crate-level attribute — applies to the whole crate
#![deny(unsafe_code)]
Basic operators

Category

Operators

Notes

Arithmetic

+ - * / %

Integer division truncates; no implicit casting

Comparison

== != < > <= >=

Returns bool; types must match exactly

Logical

&& || !

Short-circuits; operands must be bool

Bitwise

& | ^ ! << >>

Works on integer types

Assignment

= += -= *= /= %=

No ++ or -- operators in Rust

Range

.. ..=

0..5 is exclusive; 0..=5 is inclusive

A quick syntax reference

RUST
// Variable binding (immutable)
let x = 10;

// Mutable variable
let mut count = 0;

// Constant (must have explicit type)
const MAX: u32 = 1000;

// Shadowing — re-declare with the same name
let y = 5;
let y = y + 1; // new y, shadows old y

// Tuple
let point = (3.0, 4.0);
let (px, py) = point; // destructuring

// Array (fixed size, same type)
let arr = [1, 2, 3, 4, 5];

// if expression
let sign = if x > 0 { "positive" } else { "non-positive" };

// loop with a return value
let mut n = 0;
let found = loop {
    n += 1;
    if n == 5 { break n * 2; }
};
// found == 10

// while loop
while count < 10 {
    count += 1;
}

// for loop over a range
for i in 0..5 {
    println!("{}", i);
}
Note
Rust has no `++` or `--` operators. Use `+= 1` and `-= 1` instead. This removes a common class of off-by-one bugs found in C-family languages.
Success
Rust's strict compiler is your friend. Every syntax error comes with a precise message, often including the exact fix. Treat compiler errors as free code review from an expert.
Tip
Run `rustfmt` (via `cargo fmt`) regularly. It enforces the official style guide automatically, so you never have to argue about indentation or brace placement.