<< Home

Chapter 2: Language Basics

This chapter introduces the core pieces of Rust syntax that you will see throughout the rest of the language. These ideas set the foundation for everything that follows. By the end of this chapter you will understand the structure of a Rust file, the rules around variables, how types behave and how Rust organises simple units of code. These basics are small but important, and learning them early keeps later chapters clear.

Keywords, items, and files

Rust programs are built from items. An item is any top level construct such as a function, struct, enum, module or constant. Items live inside files, and Rust treats each file as part of a module tree that matches the directory layout of your project. This approach keeps programs tidy and encourages clear separation of concerns.

Keywords are reserved words such as fn, struct and mod. They tell the compiler how to interpret the parts of a file. You cannot use keywords as variable names because they have fixed meaning. Rust keeps its keyword list modest so most names remain available for everyday code.

💡 If you are unsure whether a word is reserved you can try it in a simple program. The compiler will tell you if the name has a special role.

Each source file ends in .rs and contains one or more items. A project usually starts with main.rs for binary programs or lib.rs for libraries. Cargo treats these as entry points and builds the module tree from there.

Variables, mutability, and shadowing

Variables in Rust are immutable by default. This means that once a value is bound to a name it cannot change unless you explicitly mark it as mutable with mut. This rule prevents accidental modification of data and makes code easier to reason about.

let x = 5;
let mut y = 10;

Rust also supports shadowing, which allows a new variable with the same name to replace an older one in the same scope. This is useful when you want to transform a value while keeping a clean name. Shadowing does not mutate data in place; it creates a new binding.

let name = "Alice";
let name = "Bob";
println!("{}", name);  // prints Bob

The first binding creates name with one value. The second line creates a brand new name in the same scope. The earlier binding is no longer reachable after this point. Or with a small inner block:

let count = 5;
{
  let count = 8;          // shadows outer count only inside this block
  println!("{}", count);  // prints 8
}
println!("{}", count);    // prints 5

Mutability and shadowing serve different purposes. Mutability changes the value behind a name. Shadowing creates a new name with a new value. Both appear often in everyday Rust code.

Scalar and compound types

Rust groups built in types into two families. Scalar types represent simple values such as integers, floating point numbers, booleans and characters. These are basic building blocks for calculations and control flow.

Compound types combine several values into one unit. Rust provides tuples and arrays as built in compound types. Tuples can mix types and have a fixed length. Arrays hold a fixed number of values of one type. Both appear frequently when structuring small pieces of data before moving on to richer types like structs.

⚠️ Arrays in Rust have a length that is part of their type. The notation [i32; 3] means an array of three i32 values. An i32 is a 32 bit signed integer, and the number after the semicolon shows the fixed length of the array. Because the length is included in the type, [i32; 3] and [i32; 4] are treated as completely different types by the compiler.

Most real programs combine these basic types to form shapes that match the problem domain. The goal here is simply to understand the building blocks before struct types and enums arrive in later chapters.

Statements, expressions, and blocks

Rust distinguishes between statements and expressions. A statement performs an action. An expression produces a value. Many parts of Rust code are expressions, including blocks wrapped in braces. This means that a block can return a value and be used wherever an expression is expected.

let result = {
  let a = 2;
  let b = 3;
  a + b
};
⚠️ Rust treats blocks as expressions, which is different from many languages. If the last line in a block has no semicolon, that line becomes the value the block returns. Adding a semicolon turns it into a statement instead. This behaviour can feel unusual at first, but it becomes familiar once you read more Rust code.

Understanding expressions early makes control flow and function writing easier because many Rust constructs rely on this behaviour.

Comments and documentation with rustdoc

Rust supports standard line comments beginning with // and block comments enclosed in /* … */. These are suitable for everyday notes and explanations inside your code. Clear comments make intent visible and help future readers follow your logic.

Rust also includes a documentation system called rustdoc. Documentation comments begin with /// and attach to the item that follows them. When you run cargo doc your project gains a full set of generated documentation pages that include your comments, type signatures and examples.

/// Adds two numbers together.
fn add(a: i32, b: i32) -> i32 {
  a + b
}
⚠️ Rust is strict about types, so this function tells the compiler three things very clearly. First, a must be an i32 which is a 32 bit signed integer. Second, b must also be an i32. Finally, the function returns an i32. Rust does not perform automatic numeric conversions. If you need to convert a value when calling the function you must do it explicitly, for example add(5u8 as i32, 10). You will see how this works in more detail when we explore Rust's numeric types.

These documentation comments support Markdown, example blocks and links. Larger projects often rely on them heavily because they help keep code and explanation in the same place. Over time this becomes one of Rust’s strengths since well documented code remains easier to maintain.