This chapter introduces the fundamental building blocks of a Rust program, drawing parallels and highlighting differences with C and other systems programming languages. While C programmers will recognize many syntactic elements, Rust introduces distinct concepts like ownership, strong static typing enforced by the compiler, and a powerful concurrency model—all designed to bolster memory safety and programmer expressiveness without sacrificing performance.
Throughout this overview, we’ll compare Rust’s syntax and conventions with those of C, using concise examples to illustrate key ideas. Readers with some prior exposure to Rust may choose to skim this chapter, though it offers a helpful summary of the language’s key concepts.
Later chapters will delve into each topic comprehensively. This initial tour aims to provide a general feel for the language, offer a starting point for experimentation, and demystify essential Rust features—such as the println! macro—that appear early on, before their formal explanation.
rustc and CargoLike C, Rust is a compiled language. The Rust compiler, rustc, translates Rust source code files (ending in .rs) into executable binaries or libraries. However, the Rust ecosystem centers around Cargo, an integrated build system and package manager that significantly simplifies project management and compilation compared to traditional C workflows.
Cargo acts as a unified frontend for compiling code, managing external libraries (called “crates” in Rust), running tests, generating documentation, and much more. It combines the roles often handled by separate tools like make, cmake, package managers (like apt or vcpkg for dependencies), and testing frameworks.
Creating and building a new Rust project with Cargo:
# Create a new binary project named 'my_project'
cargo new my_project
cd my_project
# Compile the project
cargo build
# Compile and run the project
cargo runCargo enforces a standard project layout (placing source code in src/ and project metadata, including dependencies, in Cargo.toml), promoting consistency across Rust projects.
A typical Rust program is composed of several elements:
struct, enum, or type aliases (type).use Statements: Import items (functions, types, etc.) from other modules or external crates into the current scope.Rust uses curly braces {} to define code blocks, similar to C. These blocks delimit scopes for functions, loops, conditionals, and other constructs. Variables declared within a block are local to that scope. Crucially, when a variable goes out of scope, Rust automatically calls its “drop” logic, freeing associated memory and releasing resources like file handles or network sockets—a core aspect of Rust’s resource management (RAII - Resource Acquisition Is Initialization).
Unlike C, Rust generally does not require forward declarations for functions or types within the same module; you can call a function defined later in the file. This often encourages a top-down code organization.
Important Exception: Variables must be declared or defined before they are used within a scope.
Items like functions or type definitions can be nested within other items (e.g., helper functions inside another function) where it enhances organization.
main Function: The Entry PointExecution of a Rust binary begins at the main function, just like in C. By convention, this function often resides in a file named src/main.rs within a Cargo project. A project can contain multiple .rs files organized into modules and potentially link against library crates.
fn main() {
println!("Hello, world!");
}fn: Keyword to declare a function.main: The special name for the program’s entry point.(): Parentheses enclose the function’s parameter list (empty in this case).{}: Curly braces enclose the function’s body.println!: A macro (indicated by the !) for printing text to the standard output, followed by a newline.;: Semicolons terminate most statements.#include <stdio.h>
int main(void) { // Or int main(int argc, char *argv[])
printf("Hello, world!\n");
return 0; // Return 0 to indicate success
}main typically returns an int status code (0 for success).main function, by default, returns the unit type (), implicitly indicating success. It can be declared to return a Result type for more explicit error handling, as we’ll see later.Variables are declared using the let keyword. A fundamental difference from C is that Rust variables are immutable by default.
let variable_name: OptionalType = value;= to perform assignments.fn main() {
let x: i32 = 5; // x is immutable
// x = 6; // This line would cause a compile-time error!
println!("The value of x is: {}", x);
}The // syntax denotes a single-line comment. Immutability helps prevent accidental modification, making code easier to reason about and enabling compiler optimizations.
To allow a variable’s value to be changed, use the mut keyword.
fn main() {
let mut x = 5; // x is mutable
println!("The initial value of x is: {}", x);
x = 6;
println!("The new value of x is: {}", x);
}The {} syntax within the println! macro string is used for string interpolation, embedding the value of variables or expressions directly into the output.
In C, variables are mutable by default. The const keyword is used to declare variables whose values should not be changed, though the level of enforcement can vary (e.g., const pointers).
int x = 5;
x = 6; // Allowed
const int y = 5;
// y = 6; // Error: assignment of read-only variable 'y'Rust is a statically typed language, meaning the type of every variable must be known at compile time. The compiler can often infer the type, but you can also provide explicit type annotations. Once assigned, a variable’s type cannot change.
Rust offers a standard set of primitive types:
i8, i16, i32, i64, i128, isize) and unsigned (u8, u16, u32, u64, u128, usize). The number indicates the bit width. isize and usize are pointer-sized integers (like ptrdiff_t and size_t in C).f32 (single-precision) and f64 (double-precision).bool (can be true or false).char represents a Unicode scalar value (4 bytes), capable of holding characters like ‘a’, ‘國’, or ‘😂’. This contrasts with C’s char, which is typically a single byte.In addition to value types like i32, Rust also supports references—safe, managed pointers that refer to data stored elsewhere in memory. Similar to C pointers, references hold the address of a value, introducing a level of indirection.
Rust references can be either immutable or mutable, allowing temporary access to data without transferring ownership or making a copy. This is especially useful for passing data to functions efficiently.
To create a reference, Rust uses the & operator for immutable access and &mut for mutable access. The * operator can be used to access (dereference) the value behind a reference, although in many cases this happens implicitly.
References are covered in more depth in Chapter 5. Chapter 6 will explore them in full detail, as part of the discussion on Ownership, Borrowing, and Memory Management.
Below is a short example demonstrating how to pass a mutable reference to a function:
fn inc(i: &mut i32) {
*i += 1;
}
fn main() {
let mut v = 0;
inc(&mut v);
println!("{v}"); // 1
let r = &mut v;
inc(r);
println!("{}", *r); // 2
}The compiler can often deduce the type based on the assigned value and context.
fn main() {
let answer = 42; // Type i32 inferred by default for integers
let pi = 3.14159; // Type f64 inferred by default for floats
let active = true; // Type bool inferred
println!("answer: {}, pi: {}, active: {}", answer, pi, active);
}Use a colon : after the variable name to specify the type explicitly, which is necessary when the compiler needs guidance or you want a non-default type (e.g., f32 instead of f64).
fn main() {
let count: u8 = 10; // Explicitly typed as an 8-bit unsigned integer
let temperature: f32 = 21.5; // Explicitly typed as a 32-bit float
println!("count: {}, temperature: {}", count, temperature);
}In C, basic types like int can have platform-dependent sizes. C99 introduced fixed-width integer types in <stdint.h> (e.g., int32_t, uint8_t), which correspond directly to Rust’s integer types. C lacks built-in type inference like Rust’s.
Rust offers two ways to define values with fixed meaning or location:
const)Constants represent values that are known at compile time. They must be annotated with a type and are typically defined in the global scope, though they can also be defined within functions. Constants are effectively inlined wherever they are used and do not have a fixed memory address. The naming convention is SCREAMING_SNAKE_CASE.
const SECONDS_IN_MINUTE: u32 = 60;
const PI: f64 = 3.1415926535;
fn main() {
println!("One minute has {} seconds.", SECONDS_IN_MINUTE);
println!("Pi is approximately {}.", PI);
}static)Static variables represent values that have a fixed memory location ('static lifetime) throughout the program’s execution. They are initialized once, usually when the program starts. Like constants, they must have an explicit type annotation. The naming convention is also SCREAMING_SNAKE_CASE.
static APP_NAME: &str = "Rust Explorer"; // A static string literal
fn main() {
println!("Welcome to {}!", APP_NAME);
}Rust strongly discourages mutable static variables (static mut) because modifying global state without synchronization can easily lead to data races in concurrent code. Accessing or modifying static mut variables requires unsafe blocks.
const is similar in spirit to C’s #define for simple values but is type-checked and integrated into the language, avoiding preprocessor pitfalls. It’s also akin to highly optimized const variables in C.static is closer to C’s global or file-scope static variables regarding lifetime and memory location. However, Rust’s emphasis on safety around mutable statics is much stricter than C’s.Functions are defined using the fn keyword, followed by the function name, parameter list (with types), and an optional return type specified after ->.
// Function that takes two i32 parameters and returns an i32
fn add(a: i32, b: i32) -> i32 {
// The last expression in a block is implicitly returned
// if it doesn't end with a semicolon.
a + b
}
// Function that takes no parameters and returns nothing (unit type `()`)
fn greet() {
println!("Hello from the greet function!");
// No return value needed, implicit `()` return
}
fn main() {
let sum = add(5, 3);
println!("5 + 3 = {}", sum);
greet();
}Key Points (Functions):
->. If omitted, the function returns the unit type ().return keyword can be used for early returns.In Rust, methods are similar to functions but are defined within impl blocks and are associated with a specific type (like a struct or enum). The first parameter of a method is usually self, &self, or &mut self, which refers to the instance the method is called on—similar to the implicit this pointer in C++.
Methods are called using dot notation: instance.method() and can be chained.
struct Point {
x: i32,
y: i32,
}
impl Point {
// Method that calculates the distance from the origin
fn magnitude(&self) -> f64 {
// Calculate square of components, cast i32 to f64 for sqrt
((self.x.pow(2) + self.y.pow(2)) as f64).sqrt()
}
}
fn main() {
let p = Point { x: 3, y: 4 };
println!("Distance from origin: {}", p.magnitude());
}Key Points (Methods):
impl blocks.self, &self, or &mut self, representing the instance..) syntax.self parameter (e.g., String::new()) are called associated functions. These are often used as constructors or for operations related to the type but not a specific instance.#include <stdio.h>
// Function declaration (prototype) often needed in C
int add(int a, int b);
void greet(void);
int main() {
int sum = add(5, 3);
printf("5 + 3 = %d\n", sum);
greet();
return 0;
}
// Function definition
int add(int a, int b) {
return a + b; // Explicit return statement required
}
void greet(void) {
printf("Hello from the greet function!\n");
// No return statement needed for void functions
}return statement for functions returning values. Rust allows implicit returns via the last expression.Rust provides standard control flow structures, but with some differences compared to C, particularly regarding conditions and loops.
if, else if, and elsefn 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");
}
}As in C, Rust uses % for the modulo operation and == to test for equality.
Conditions must evaluate to a bool. Unlike C, integers are not automatically treated as true (non-zero) or false (zero).
Parentheses () around the condition are not required.
Curly braces {} around the blocks are mandatory, even for single statements, preventing potential dangling else issues.
if is an expression in Rust, meaning it can return a value:
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 }; // `if` as an expression
println!("The number is {}", number);
}loop, while, and forRust offers three looping constructs:
loop: Creates an infinite loop, typically exited using break. break can also return a value from the loop.
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // Exit loop and return counter * 2
}
};
println!("The loop result is {}", result); // Prints 20
}while: Executes a block as long as a boolean condition remains true.
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}for: Iterates over elements produced by an iterator. This is the most common and idiomatic loop in Rust. It’s fundamentally different from C’s typical index-based for loop.
fn main() {
// Iterate over a range (0 to 4)
for i in 0..5 {
println!("The number is: {}", i);
}
// Iterate over elements of an array
let a = [10, 20, 30, 40, 50];
// `.iter()` creates an iterator over references; inferred since Rust 2021
for element in a { // or explicitly `a.iter()`
println!("The value is: {}", element);
}
}
There is no direct equivalent to C’s for (int i = 0; i < N; ++i) construct in Rust. Range-based for loops or explicit iterator usage are preferred for safety and clarity.
continue: Skips the rest of the current iteration and proceeds to the next one, usable in all loop types.
bool conditions in if and while. C allows integer conditions (0 is false, non-zero is true).{} for if/else/while/for blocks. C allows omitting them for single statements, which can be error-prone.for loop is exclusively iterator-based. C’s for loop is a general structure with initialization, condition, and increment parts.if conditions (e.g., if x = y { ... } is an error), avoiding a common C pitfall (if (x = y) vs. if (x == y)).match, a powerful pattern-matching construct (covered later) that is often more versatile than C’s switch.Modules encapsulate Rust source code, hiding internal implementation details. Crates are the fundamental units of code compilation and distribution in Rust.
mod)Modules provide namespaces and control the visibility of items (functions, structs, etc.). Items within a module are private by default and must be explicitly marked pub (public) to be accessible from outside the module.
// Define a module named 'greetings'
mod greetings {
// This function is private to the 'greetings' module
fn default_greeting() -> String {
// `to_string` is a method that converts a string literal (&str)
// into an owned String.
"Hello".to_string()
}
// This function is public and can be called from outside
pub fn spanish() {
println!("{} in Spanish is Hola!", default_greeting());
}
// Modules can be nested
pub mod casual {
pub fn english() {
println!("Hey there!");
}
}
}
fn main() {
// Call public functions using the module path `::`
greetings::spanish();
greetings::casual::english();
// greetings::default_greeting(); // Error: private function
}For larger projects, a module’s contents can be placed in a separate file instead of directly within its parent file. When you declare a module using mod my_module; in a file (e.g., main.rs or lib.rs), the compiler looks for the module’s code in one of two locations:
my_module.rs: A file named my_module.rs located in the same directory as the declaring file. This is the preferred convention since the Rust 2018 edition.my_module/mod.rs: A file named mod.rs inside a subdirectory named my_module/. This is an older convention but still supported.Cargo handles the process of finding and compiling these files automatically based on the mod declarations.
A crate is the smallest unit of compilation and distribution in Rust. There are two types:
main function (like the my_project example earlier).main function). Compiled into a .rlib file by default (Rust’s static library format).A Cargo project (package) can contain one library crate and/or multiple binary crates.
.h) and source (.c) files along with #include. Rust modules provide stronger encapsulation and avoid issues related to textual inclusion, multiple includes, and managing include guards.use Keyword: Bringing Paths into ScopeThe use keyword shortens the paths needed to refer to items (functions, types, modules) defined elsewhere, making code less verbose.
Instead of writing the full path repeatedly, use brings the item into the current scope.
// Bring the `io` module from the standard library (`std`) into scope
use std::io;
// Bring a specific type `HashMap` into scope
use std::collections::HashMap;
fn main() {
// Now we can use `io` directly instead of `std::io`
let mut input = String::new(); // String::new() is an associated function
println!("Enter your name:");
// stdin(), read_line(), and expect() are methods
io::stdin().read_line(&mut input).expect("Failed to read line");
// Use HashMap directly
let mut scores = HashMap::new(); // HashMap::new() is an associated function
scores.insert(String::from("Alice"), 10); // insert() is a method
// trim() is a method
println!("Hello, {}", input.trim());
// get() is a method, {:?} is debug formatting
println!("Alice's score: {:?}", scores.get("Alice"));
}String::new() and HashMap::new() are associated functions acting like constructors.io::stdin() gets a handle to standard input. read_line(), expect(), insert(), trim(), and get() are methods called on instances or intermediate results.read_line(&mut input) reads a line into the mutable string input. The &mut indicates a mutable borrow, allowing read_line to modify input without taking ownership (more on borrowing later)..expect(...) handles potential errors, crashing the program if the preceding operation (like read_line or potentially get) returns an error or None. Result and Option (covered next) offer more robust error handling.Note: Running this code in environments like the Rust Playground or mdbook might not capture interactive input correctly.
C’s #include directive performs textual inclusion of header files before compilation. Rust’s use statement operates at a semantic level, importing specific namespaced items without code duplication, leading to faster compilation and clearer dependency tracking.
Traits define a set of methods that a type must implement, serving a purpose similar to interfaces in other languages or abstract base classes in C++. They are fundamental to Rust’s approach to abstraction and code reuse, allowing different types to share common functionality.
A trait is defined using the trait keyword, followed by the trait name and a block containing the signatures of the methods that implementing types must provide.
// Define a trait named 'Drawable'
trait Drawable {
// Method signature: takes an immutable reference to self, returns nothing
fn draw(&self);
}Types implement traits using an impl Trait for Type block, providing concrete implementations for the methods defined in the trait.
// Define a simple struct
struct Circle;
// Implement the 'Drawable' trait for the 'Circle' struct
impl Drawable for Circle {
// Provide the concrete implementation for the 'draw' method
fn draw(&self) {
println!("Drawing a circle");
}
}Once a type implements a trait, you can call the trait’s methods on instances of that type.
fn main() {
let shape1 = Circle;
// Call the 'draw' method defined by the 'Drawable' trait
shape1.draw(); // Output: Drawing a circle
}C lacks a direct equivalent to traits. Achieving similar polymorphism typically involves using function pointers, often grouped within structs (sometimes referred to as “vtables”). This approach requires manual setup and management, lacks the compile-time verification provided by Rust’s trait system, and can be more error-prone. Rust’s traits provide a safer, more integrated way to define and use shared behavior across different types.
Macros in Rust are a powerful feature for metaprogramming—writing code that generates other code at compile time. They operate on Rust’s abstract syntax tree (AST), making them more robust and integrated than C’s text-based preprocessor macros.
macro_rules!, these work based on pattern matching and substitution. println!, vec!, and assert_eq! are common examples.#[derive(Debug)]).// A simple declarative macro
macro_rules! create_function {
// Match the identifier passed (e.g., `my_func`)
($func_name:ident) => {
// Generate a function with that name
fn $func_name() {
// Use stringify! to convert the identifier to a string literal
println!("You called function: {}", stringify!($func_name));
}
};
}
// Use the macro to create a function named 'hello_macro'
create_function!(hello_macro);
fn main() {
// Call the generated function
hello_macro();
}println! vs. C’s printfThe println! macro (and its relative print!) performs format string checking at compile time. This prevents runtime errors common with C’s printf family, where mismatches between format specifiers (%d, %s) and the actual arguments can lead to crashes or incorrect output.
// C preprocessor macro for squaring (prone to issues)
#define SQUARE(x) x * x // Problematic if called like SQUARE(a + b) -> a + b * a + b
// Better C macro
#define SQUARE_SAFE(x) ((x) * (x))C macros perform simple text substitution, which can lead to unexpected behavior due to operator precedence or multiple evaluations of arguments. Rust macros operate on the code structure itself, avoiding these pitfalls.
Result and OptionRust primarily handles errors using two special enumeration types provided by the standard library, eschewing exceptions found in languages like C++ or Java.
Result<T, E>Result is used for operations that might fail in a recoverable way (e.g., file I/O, network requests, parsing). It has two variants:
Ok(T): Contains the success value of type T.Err(E): Contains the error value of type E.fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
// `trim()` and `parse()` are methods called on the string slice `s`.
// `parse()` returns a Result.
s.trim().parse()
}
fn main() {
let strings_to_parse = ["123", "abc", "-45"]; // Array of strings to attempt parsing
for s in strings_to_parse { // Iterate over the array
println!("Attempting to parse '{}':", s);
match parse_number(s) {
Ok(num) => println!(" Success: Parsed number: {}", num),
Err(e) => println!(" Error: {}", e), // Display the specific parse error
}
}
}The match statement is commonly used to handle both variants of a Result.
Option<T>Option is used when a value might be present or absent (similar to handling null pointers, but safer). It has two variants:
Some(T): Contains a value of type T.None: Indicates the absence of a value.fn find_character(text: &str, ch: char) -> Option<usize> {
// `find()` is a method on string slices that returns Option<usize>.
text.find(ch)
}
fn main() {
let text = "Hello Rust";
let chars_to_find = ['R', 'l', 'z']; // Array of characters to search for
println!("Searching in text: \"{}\"", text);
for ch in chars_to_find { // Iterate over the array
println!("Searching for '{}':", ch);
match find_character(text, ch) {
Some(index) => println!(" Found at index: {}", index),
None => println!(" Not found"),
}
}
}C traditionally handles errors using return codes (e.g., -1, NULL) combined with a global errno variable, or by passing pointers for output values and returning a status code. These approaches require careful manual checking and can be ambiguous or easily forgotten. Rust’s Result and Option force the programmer to explicitly acknowledge and handle potential failures or absence at compile time, leading to more robust code.
One of Rust’s defining features is its ability to guarantee memory safety (no dangling pointers, no use-after-free, no data races) at compile time without requiring a garbage collector (GC). This is achieved through its ownership and borrowing system:
&T) or mutable (&mut T). Rust enforces strict rules: you can have multiple immutable references or exactly one mutable reference to a particular piece of data in a particular scope, but not both simultaneously.This system eliminates many common bugs found in C/C++ related to manual memory management while providing performance comparable to C/C++.
C relies on manual memory management (malloc, calloc, realloc, free). This gives programmers fine-grained control but makes it easy to introduce errors like memory leaks (forgetting free), double frees, use-after-free, and buffer overflows. Rust’s compiler acts as a vigilant checker, preventing these issues before the program even runs.
Rust is primarily an expression-based language. This means most constructs, including if blocks, match arms, and even simple code blocks {}, evaluate to a value.
5, x + 1, if condition { val1 } else { val2 }, { let a = 1; a + 2 }).;. The semicolon discards the value of the expression, turning it into a statement. Variable declarations with let are also statements.fn main() {
// `let y = ...` is a statement.
// The block `{ ... }` is an expression.
let y = {
let x = 3;
x + 1 // No semicolon: this is the value the block evaluates to
}; // Semicolon ends the `let` statement.
println!("The value of y is: {}", y); // Prints 4
// Example of an if expression
let condition = false;
let z = if condition { 10 } else { 20 };
println!("The value of z is: {}", z); // Prints 20
// Example of a statement (discarding the block's value)
{
println!("This block doesn't return a value to assign.");
}; // Semicolon is optional here as it's the last thing in `main`'s block
}In C, the distinction between expressions and statements is stricter. For example, if/else constructs are statements, not expressions, and blocks {} do not inherently evaluate to a value that can be assigned directly. Assignments themselves (x = 5) are expressions in C, which allows constructs like if (x = y) that Rust prohibits in conditional contexts.
The Rust community follows fairly standardized code style and naming conventions, largely enforced by tooling.
rustfmt)rustfmt is the official tool for automatically formatting Rust code according to the standard style. Running cargo fmt applies it to the entire project. Consistent formatting enhances readability across different projects.snake_case: Variables, function names, module names, crate names (e.g., let my_variable, fn calculate_sum, mod network_utils).PascalCase (or UpperCamelCase): Types (structs, enums, traits), type aliases (e.g., struct Player, enum Status, trait Drawable).SCREAMING_SNAKE_CASE: Constants, static variables (e.g., const MAX_CONNECTIONS, static DEFAULT_PORT).C style conventions vary significantly between projects and organizations (e.g., K&R style, Allman style, GNU style). While tools like clang-format exist, there isn’t a single, universally adopted standard quite like rustfmt in the Rust ecosystem.
Rust supports several forms of comments, including special syntax for generating documentation.
// Single-line comment: Extends to the end of the line./* Multi-line comment */: Can span multiple lines. These can be nested.// Calculate the square of a number
fn square(x: i32) -> i32 {
/*
This function takes an integer,
multiplies it by itself,
and returns the result.
*/
x * x
}rustdoc)Rust has built-in support for documentation generation via the rustdoc tool, which processes special documentation comments written in Markdown.
/// Doc comment for the item following it: Used for functions, structs, modules, etc.//! Doc comment for the enclosing item: Used inside a module or crate root (lib.rs or main.rs) to document the module/crate itself.//! This module provides utility functions for string manipulation.
/// Reverses a given string slice.
///
/// # Examples
///
/// ```
/// let original = "hello";
/// # // We might hide the module path in the rendered docs for simplicity,
/// # // but it's needed here if `reverse` is in `string_utils`.
/// # mod string_utils { pub fn reverse(s: &str) -> String {s.chars().rev().collect()}}
/// let reversed = string_utils::reverse(original);
/// assert_eq!(reversed, "olleh");
/// ```
///
/// # Panics
/// This function might panic if memory allocation fails (very unlikely).
pub fn reverse(s: &str) -> String {
s.chars().rev().collect()
}
// (Module content continues...)Running cargo doc builds the documentation for your project and its dependencies as HTML files, viewable in a web browser. Code examples within /// comments (inside triple backticks ) are compiled and run as tests by cargo test, ensuring documentation stays synchronized with the code.
Multi-line doc comments /** ... */ (for following item) and /*! ... */ (for enclosing item) also exist but are less common than /// and //!.
This chapter provided a high-level tour. Many powerful Rust features build upon these basics. Here’s a glimpse of what subsequent chapters will explore in detail:
Vec<T> dynamic arrays, HashMap<K, V> hash maps), I/O, networking, threading primitives, and more. Generally more comprehensive than the C standard library.structs (like C structs), enums (more powerful than C enums, acting like tagged unions), and tuples.match, enabling exhaustive checks and destructuring of data.Mutex, Arc) that prevent data races at compile time via the Send and Sync traits.async/await syntax for non-blocking I/O, used with runtime libraries like tokio or async-std for highly concurrent applications.cargo test.unsafe Rust: A controlled escape hatch to bypass some compiler guarantees when necessary (e.g., for Foreign Function Interface (FFI), hardware interaction, or specific optimizations), clearly marking potentially unsafe code blocks.cargo build and cargo run, exploring clippy (linter for common mistakes and style issues), dependency management, workspaces, and more.This chapter offered a foundational overview of Rust program structure and syntax, contrasting it frequently with C:
cargo for building, testing, and dependency management, providing a unified experience compared to disparate C tools.fn main(). Syntax involves fn, let, mut, type annotations (:), methods (.), and curly braces {} for scopes.let), requiring mut for modification, unlike C’s default mutability.char is a 4-byte Unicode scalar value.if/else requires boolean conditions and braces. Loops include loop, while, and iterator-based for.mod) and compiled into crates (binaries or libraries), with use for importing items.fn) and methods (impl blocks, associated with types).trait) define shared behavior, while macros provide safe compile-time metaprogramming.Result<T, E> and Option<T> provide robust, explicit ways to handle potential failures and absence of values.rustfmt) and naming conventions are widely adopted.rustdoc) using Markdown comments.These elements collectively shape Rust’s focus on safety, concurrency, and performance. Armed with this basic understanding, we are now ready to delve deeper into the specific features that make Rust a compelling alternative for systems programming, starting with its fundamental data types and control flow mechanisms in the upcoming chapters.