No history yet

Introduction to Rust

Why Rust?

Rust is a systems programming language that focuses on three things: safety, speed, and concurrency. It achieves memory safety—preventing common bugs like null pointer dereferences or buffer overflows—without using a garbage collector. A garbage collector is a process that periodically cleans up unused memory, which can add overhead and unpredictable pauses to a program's execution.

Instead of a garbage collector, Rust uses a unique ownership model with a set of rules that the compiler checks at compile time. If the rules are broken, the code won't compile.

This compile-time checking means that a whole class of bugs is eliminated before the program even runs. It's what allows Rust to be both memory-safe and highly performant, making it a strong choice for everything from web servers to game engines.

Lesson image

A First Look at Syntax

Let's start with the classic "Hello, world!" program. In Rust, it looks like this:

fn main() {
    // The ! means we're calling a macro, not a normal function.
    println!("Hello, world!");
}

Functions are declared with fn. The main function is the entry point of every executable Rust program.

Variables are declared with the let keyword. By default, they are immutable, meaning their value can't be changed once set. To make a variable mutable, you use the mut keyword.

fn main() {
    // An immutable variable
    let x = 5;
    println!("The value of x is: {}", x);

    // A mutable variable
    let mut y = 10;
    println!("The value of y is: {}", y);
    y = 20;
    println!("The new value of y is: {}", y);
}

This focus on immutability by default helps prevent accidental changes to your data, which is another way Rust encourages writing safer code.

Ownership and Borrowing

Ownership is Rust's most unique feature. It's a set of rules checked by the compiler that manages memory. All programs have to manage how they use computer memory while running. Some languages use garbage collection; others require the programmer to manually allocate and free memory. Rust uses ownership.

Here are the rules:

  1. Each value in Rust has a variable that's called its owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.

When you assign a complex data type like a String to another variable, ownership is moved. The original variable is no longer valid, which prevents a bug called a double free error.

let s1 = String::from("hello");
let s2 = s1;

// This line would cause a compile error!
// s1 is no longer valid because ownership was moved to s2.
// println!("{}", s1);

What if you want to use a value without taking ownership? You can borrow it. Borrowing is done by creating a reference to the value, using the & symbol. References are immutable by default.

fn calculate_length(s: &String) -> usize {
    s.len()
} // s goes out of scope, but because it does not have ownership,
  // nothing happens.

fn main() {
    let s1 = String::from("hello");

    // We pass a reference to s1, so ownership is not moved.
    let len = calculate_length(&s1);

    // s1 is still valid here.
    println!("The length of '{}' is {}.", s1, len);
}

If you want to modify the borrowed value, you can create a mutable reference using &mut. A key rule is that you can only have one mutable reference to a particular piece of data in a particular scope. This restriction is how Rust prevents data races at compile time.

Fearless Concurrency

A data race occurs when two or more threads access the same memory location concurrently, and at least one of them is for writing, without any synchronization. This can lead to unpredictable behavior.

Rust's concurrency model leverages its ownership and borrowing rules to guarantee thread safety at compile time, eliminating data races without requiring a garbage collector.

Because the ownership rules prevent you from having multiple mutable references to the same data, it's impossible to create a data race. The compiler will simply refuse to compile the code.

Here’s how you can create a new thread:

use std::thread;
use std::time::Duration;

fn main() {
    // Spawn a new thread
    thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    // Code in the main thread
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

If you try to share data between threads in an unsafe way, Rust's compiler will stop you with an error. This compile-time checking gives developers confidence to write concurrent code without fear of introducing subtle, hard-to-find bugs.

Now that you've seen the basics, let's test your understanding.

Quiz Questions 1/5

What are the three core goals that guide the design of the Rust programming language?

Quiz Questions 2/5

How does Rust primarily achieve memory safety without a garbage collector?

These core concepts—safety, performance, and a powerful type system—are what make Rust a compelling language for modern development.