No history yet

Introduction to Rust

Why Rust?

Rust is a programming language focused on three things: speed, safety, and concurrency. For game development, this is a powerful combination. Games need to be fast to feel responsive. They also need to be reliable, because nobody likes a game that crashes. Rust helps you write code that is both fast and safe, preventing common bugs that plague other languages.

Rust is a blazing fast and memory-efficient static compiled language with a rich type system and ownership model.

It achieves this safety without a "garbage collector," a background process that cleans up memory in languages like Java or C#. This means Rust's performance is consistent and predictable, which is crucial for smooth gameplay.

Setting Up Your Environment

Getting started with Rust is straightforward. The official tool for installing Rust is called rustup. It manages your Rust installation and keeps it up to date. To install it, open your terminal (on macOS or Linux) or PowerShell (on Windows) and run this command:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Follow the on-screen instructions, and you'll have the Rust compiler (rustc), the package manager (cargo), and other tools installed.

Let's write our first program. Create a file named main.rs and add the following code.

// This is the main function where the program starts.
fn main() {
    // Print text to the console.
    println!("Hello, world!");
}

You can compile and run this file directly using the Rust compiler, rustc.

# Compile the program
rustc main.rs

# Run the executable
./main

While rustc is good for simple things, most Rust projects use Cargo, Rust's build system and package manager. It handles compiling your code, downloading libraries (called "crates"), and more.

To start a new project with Cargo, run:

cargo new hello_rust

This creates a new directory called hello_rust with a src/main.rs file and a Cargo.toml configuration file. To run your project, navigate into the directory and use:

cd hello_rust
cargo run

Cargo will compile and run the executable for you. You'll be using cargo for most of your Rust development.

The Core Idea Ownership

Rust's most unique feature is its concept of ownership. It's a set of rules the compiler checks to manage memory safely. If you understand ownership, you understand a huge part of what makes Rust special.

There are three main 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 (meaning its memory is freed).
Lesson image

Think of it like a dog on a leash. The leash is the variable, and the dog is the value in memory. Only one person can hold the leash at a time. When that person goes home (out of scope), the dog goes with them.

Let's see this in action. The String type is a good example because its data is stored on the heap.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1's ownership is moved to s2

    // This next line won't compile!
    // println!("{}", s1); 
}

In this code, when we assign s1 to s2, Rust moves ownership. s1 is no longer a valid variable because it no longer owns the "hello" string data. Only s2 does. This prevents a common bug called a "double free," where two variables might try to free the same memory.

This might seem restrictive, but what if we just want to let a function use a value without taking ownership? That's where borrowing comes in.

Borrowing and References

Borrowing in Rust means creating a reference to a value. A reference is like a pointer that lets you access data without taking ownership of it. You create a reference using the ampersand (&) symbol.

Imagine you want a friend to see your dog, but you don't want to give them the leash permanently. You just let them pet the dog while you hold the leash. They are "borrowing" your dog.

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

    // Pass a reference to the string
    let len = calculate_length(&s);

    println!("The length of '{}' is {}.", s, len);
}

// This function takes a reference to a String
fn calculate_length(some_string: &String) -> usize {
    some_string.len()
}
// `some_string` goes out of scope, but because it does not
// have ownership, the value it points to is not dropped.

Here, calculate_length takes a reference to a String. It can read the data, but it doesn't own it. After the function call, s in the main function is still valid and can be used.

Rust enforces two important rules for borrowing:

  1. You can have any number of immutable references (&T) to a value.
  2. You can only have one mutable reference (&mut T) at a time.

These rules are checked at compile time and are how Rust prevents data races—a nasty type of bug where multiple parts of a program try to change the same data at the same time. This concurrency safety is a huge benefit, especially in complex applications like games.

Quiz Questions 1/5

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

Quiz Questions 2/5

Which command should you use to create a new Rust project with the standard directory structure and a Cargo.toml file?