1. rust
  2. /basics
  3. /syntax

Rust Syntax

Rust has a syntax that draws inspiration from C-family languages while introducing unique concepts for memory safety and concurrency. Understanding Rust's syntax is essential for writing correct and idiomatic Rust code.

Basic Program Structure

Every Rust program starts with a main function:

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

Functions are declared with the fn keyword, followed by the function name, parameters in parentheses, and the function body in curly braces.

Statements vs Expressions

Rust is an expression-based language, which means most constructs return a value.

Statements

Statements perform an action and do not return a value:

fn main() {
    let x = 5;  // Statement: variable declaration
    let y = 10; // Statement: variable declaration
}

Expressions

Expressions evaluate to a value:

fn main() {
    let x = 5;
    
    // Block expression
    let y = {
        let x = 1;
        x + 1  // Expression: no semicolon
    };
    
    println!("y is: {}", y); // y is: 2
}

Variables and Immutability

By default, variables in Rust are immutable:

fn main() {
    let x = 5;
    // x = 6; // This would cause a compile error
    
    // Make variables mutable with 'mut'
    let mut y = 5;
    y = 6; // This is allowed
    
    println!("x: {}, y: {}", x, y);
}

Variable Shadowing

Rust allows variable shadowing, where you can declare a new variable with the same name:

fn main() {
    let x = 5;
    let x = x + 1;  // Shadows the previous x
    let x = x * 2;  // Shadows again
    
    println!("x is: {}", x); // x is: 12
    
    // Shadowing can change the type
    let spaces = "   ";
    let spaces = spaces.len(); // Now spaces is a number
}

Constants

Constants are declared with const and must have their type annotated:

const MAX_POINTS: u32 = 100_000;

fn main() {
    println!("Max points: {}", MAX_POINTS);
}

Functions

Function syntax includes parameters, return types, and return values:

fn main() {
    let result = add(5, 3);
    println!("5 + 3 = {}", result);
    
    let value = get_value();
    println!("Value: {}", value);
}

// Function with parameters and return type
fn add(x: i32, y: i32) -> i32 {
    x + y  // Expression return (no semicolon)
}

// Function returning a value using 'return' keyword
fn get_value() -> i32 {
    return 42;
}

// Function with no return value (returns unit type ())
fn print_hello() {
    println!("Hello!");
}

Comments

Rust supports several types of comments:

fn main() {
    // Single-line comment
    
    /*
     * Multi-line comment
     * can span multiple lines
     */
    
    /// Documentation comment for the following item
    /// These are used to generate documentation
    let x = 5;
    
    //! Inner documentation comment
    //! Documents the enclosing item
}

/// Documentation comment for a function
/// This will appear in generated docs
fn documented_function() {
    // Regular comment inside function
}

Operators

Rust provides familiar operators with some unique behaviors:

Arithmetic Operators

fn main() {
    let a = 10;
    let b = 3;
    
    println!("Addition: {}", a + b);      // 13
    println!("Subtraction: {}", a - b);   // 7
    println!("Multiplication: {}", a * b); // 30
    println!("Division: {}", a / b);      // 3 (integer division)
    println!("Remainder: {}", a % b);     // 1
}

Comparison Operators

fn main() {
    let a = 5;
    let b = 10;
    
    println!("Equal: {}", a == b);        // false
    println!("Not equal: {}", a != b);    // true
    println!("Less than: {}", a < b);     // true
    println!("Greater than: {}", a > b);  // false
    println!("Less or equal: {}", a <= b); // true
    println!("Greater or equal: {}", a >= b); // false
}

Logical Operators

fn main() {
    let a = true;
    let b = false;
    
    println!("AND: {}", a && b);  // false
    println!("OR: {}", a || b);   // true
    println!("NOT: {}", !a);      // false
}

Assignment Operators

fn main() {
    let mut x = 10;
    
    x += 5;  // x = x + 5
    println!("x after += 5: {}", x); // 15
    
    x -= 3;  // x = x - 3
    println!("x after -= 3: {}", x); // 12
    
    x *= 2;  // x = x * 2
    println!("x after *= 2: {}", x); // 24
    
    x /= 4;  // x = x / 4
    println!("x after /= 4: {}", x); // 6
}

Control Flow

If Expressions

fn main() {
    let number = 6;
    
    if number % 4 == 0 {
        println!("Number is divisible by 4");
    } else if number % 3 == 0 {
        println!("Number is divisible by 3");
    } else if number % 2 == 0 {
        println!("Number is divisible by 2");
    } else {
        println!("Number is not divisible by 4, 3, or 2");
    }
    
    // if as an expression
    let condition = true;
    let value = if condition { 5 } else { 6 };
    println!("Value: {}", value);
}

Loops

fn main() {
    // Infinite loop
    let mut counter = 0;
    loop {
        counter += 1;
        if counter == 5 {
            break;
        }
        println!("Counter: {}", counter);
    }
    
    // While loop
    let mut number = 3;
    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }
    println!("LIFTOFF!!!");
    
    // For loop
    let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
        println!("Value: {}", element);
    }
    
    // For loop with range
    for number in 1..4 {
        println!("{}!", number);
    }
}

Pattern Matching

The match expression is fundamental to Rust:

fn main() {
    let value = 3;
    
    match value {
        1 => println!("One"),
        2 => println!("Two"),
        3 => println!("Three"),
        _ => println!("Something else"),
    }
    
    // Match with multiple patterns
    let x = 1;
    match x {
        1 | 2 => println!("One or two"),
        3..=5 => println!("Three through five"),
        _ => println!("Something else"),
    }
}

Macros

Macros are called with ! and can take various argument patterns:

fn main() {
    // println! macro
    println!("Hello, world!");
    
    // Formatting
    let name = "Rust";
    println!("Hello, {}!", name);
    
    // vec! macro to create vectors
    let v = vec![1, 2, 3, 4, 5];
    
    // panic! macro for unrecoverable errors
    // panic!("This will crash the program");
}

Attributes

Attributes provide metadata about code:

// Outer attribute (applies to the following item)
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 1, y: 2 };
    println!("{:?}", p);
}

// Conditional compilation
#[cfg(target_os = "windows")]
fn platform_specific() {
    println!("This only compiles on Windows");
}

// Allow certain warnings
#[allow(dead_code)]
fn unused_function() {
    // This function won't trigger a warning
}

Keywords

Rust has several categories of keywords:

Current Keywords

// These are currently used keywords
// as, break, const, continue, crate, else, enum, extern,
// false, fn, for, if, impl, in, let, loop, match, mod,
// move, mut, pub, ref, return, self, Self, static, struct,
// super, trait, true, type, unsafe, use, where, while

Reserved Keywords

// These are reserved for future use
// abstract, become, box, do, final, macro, override,
// priv, typeof, unsized, virtual, yield

String Literals

Rust provides several ways to write strings:

fn main() {
    // Regular string literal
    let s1 = "Hello, world!";
    
    // Raw string literal (no escape sequences)
    let s2 = r"C:\Users\name\file.txt";
    
    // Multi-line string
    let s3 = "This is a
    multi-line string";
    
    // Raw multi-line string
    let s4 = r#"
    This is a raw
    multi-line string
    with "quotes"
    "#;
    
    // Byte string
    let bytes = b"Hello";
    
    println!("{}", s1);
}

Numeric Literals

Rust supports various numeric literal formats:

fn main() {
    // Decimal
    let decimal = 42;
    
    // Hexadecimal
    let hex = 0xff;
    
    // Octal
    let octal = 0o77;
    
    // Binary
    let binary = 0b1111_0000;
    
    // Byte (u8 only)
    let byte = b'A';
    
    // Visual separators
    let million = 1_000_000;
    
    // Type suffixes
    let x = 42u32;
    let y = 3.14f64;
    
    println!("Decimal: {}, Hex: {}, Binary: {}", decimal, hex, binary);
}

Semicolons and Expressions

Understanding when to use semicolons is crucial:

fn main() {
    // Statements end with semicolons
    let x = 5;
    let y = 10;
    
    // Expression blocks return the last expression
    let z = {
        let inner = x + y;
        inner * 2  // No semicolon = returned value
    };
    
    println!("z is: {}", z); // z is: 30
    
    // Adding semicolon makes it a statement
    let w = {
        let inner = x + y;
        inner * 2; // Semicolon = returns ()
    };
    
    // w is now () (unit type)
    println!("w is: {:?}", w); // w is: ()
}

Code Organization

Modules

// Declare a module
mod my_module {
    pub fn public_function() {
        println!("This is public");
    }
    
    fn private_function() {
        println!("This is private");
    }
}

fn main() {
    my_module::public_function();
    // my_module::private_function(); // Error: private
}

Use Statements

use std::collections::HashMap;
use std::fmt::{Display, Debug};

fn main() {
    let mut map = HashMap::new();
    map.insert("key", "value");
}

Common Syntax Patterns

Variable Declarations

fn main() {
    let x = 5;                    // Immutable binding
    let mut y = 5;                // Mutable binding
    let (a, b) = (1, 2);         // Destructuring tuple
    let [first, second] = [1, 2]; // Destructuring array
}

Function Calls

fn main() {
    let result = function_name(arg1, arg2);
    let result = object.method_name(arg1);
    let result = Type::associated_function(arg1);
}

Error Handling

fn main() {
    // Using Result with match
    match std::fs::read_to_string("file.txt") {
        Ok(contents) => println!("{}", contents),
        Err(error) => println!("Error: {}", error),
    }
    
    // Using ? operator (in functions that return Result)
    // let contents = std::fs::read_to_string("file.txt")?;
}

Understanding Rust's syntax is the foundation for writing effective Rust code. The language's expression-based nature, strict type system, and ownership rules all work together to create a powerful and safe programming environment.