Rust is a systems programming language focused on safety, speed, and concurrency. It guarantees memory safety without needing a garbage collector, making it ideal for applications that require high performance, such as game engines, operating systems, and web servers. Its modern syntax, coupled with its emphasis on safe code, makes it an increasingly popular choice among developers.
To get started with Rust, you need to install it on your machine. Follow these steps:
-
Open your terminal (Command Prompt for Windows, Terminal for macOS/Linux).
-
Run the following command to install Rust using the official installation tool,
rustup:curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
-
Follow the on-screen instructions to complete the installation.
- On Windows, you can alternatively use the official Rust installer here.
Once installed, you can verify the installation by running:
rustc --version
This should output the version of Rust you have installed.
-
Creating a New Project
To create a new Rust project, navigate to the folder where you want your project to reside and run:
cargo new project_name cd project_namecargois Rust's build system and package manager, and this command sets up a new project with the necessary files. -
Compiling and Running a File
Inside your new project folder, you’ll find a
src/main.rsfile. To compile and run your project, use the following command:cargo run
This will compile the code and run the resulting executable.
-
Testing Your Project
Rust includes a built-in testing framework. To run tests, you can add test functions to your code, then use:
cargo test
For this course, we recommend using Visual Studio Code as your code editor. Here’s how to set it up:
-
Install VS Code
If you don’t already have VS Code installed, you can download it here. -
Rust Extensions for VS Code
To improve your Rust development experience, install the following extensions:
- Rust Analyzer: This provides autocompletion, code navigation, and more.
- CodeLLDB: A debugger for Rust.
After installing these extensions, you'll be ready to develop, debug, and run Rust code directly within VS Code.
In Rust, every value has a data type. The data type tells Rust what kind of data is being used. Some common data types are:
-
Integers: Whole numbers like
5,-3. Examples:let x: i32 = 10; // 32-bit integer let y: u8 = 255; // Unsigned 8-bit integer (positive only)
-
Floating Points: Numbers with decimals like
4.2. Examples:let z: f64 = 3.14; // 64-bit floating point
-
Booleans: True or false values. Example:
let is_active: bool = true;
-
Characters: Letters or symbols like
aor#. Example:let letter: char = 'A';
Different types use different amounts of memory. For example:
i32uses 32 bits (4 bytes).f64uses 64 bits (8 bytes).
You can check the size of any type using this code:
println!("Size of i32: {} bytes", std::mem::size_of::<i32>());Rust stores variables in two places: the stack and the heap.
- Stack: Fast, but stores small, simple data types (like integers).
- Heap: Slower, but stores larger data like strings.
Example:
let x = 5; // Stored on the stack
let s = String::from("Hello"); // Stored on the heapVariables in Rust have a scope, meaning they only exist in certain parts of the program. When a variable goes out of scope, it’s removed from memory.
Example:
{
let x = 5; // x is in scope
println!("{}", x);
} // x goes out of scope hereShadowing lets you declare a variable with the same name, but change its value or type.
Example:
let x = 5;
let x = x + 1; // x is now 6
let x = "Hello"; // x is now a stringRust supports basic math operations:
+(Addition)-(Subtraction)*(Multiplication)/(Division)%(Modulus)
Example:
let sum = 5 + 10;
let product = 4 * 3;
let remainder = 10 % 3;Conditional operators allow you to compare values:
==(Equal to)!=(Not equal to)>(Greater than)<(Less than)
Example:
let x = 5;
if x > 3 {
println!("x is greater than 3");
} else {
println!("x is 3 or less");
}This module explains the basic data types and memory handling in Rust. Understanding these topics will help you write safe and efficient Rust code!
Conditionals in Rust allow you to make decisions in your code using if, else if, and else.
Example:
let x = 5;
if x > 10 {
println!("x is greater than 10");
} else if x == 5 {
println!("x is equal to 5");
} else {
println!("x is less than 10");
}You can also use if in a let statement to assign a value based on a condition:
let number = if x > 5 { 10 } else { 0 };
println!("The value is {}", number);Rust has different kinds of loops: loop, while, and for.
The loop keyword creates an infinite loop until you explicitly tell it to stop using break.
Example:
let mut counter = 0;
loop {
counter += 1;
if counter == 5 {
break; // Exit the loop when counter is 5
}
println!("Counter is at {}", counter);
}A while loop will run as long as a condition is true.
Example:
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("Liftoff!");A for loop is useful for iterating over a collection of values.
Example:
let a = [10, 20, 30, 40, 50];
for element in a {
println!("The value is: {}", element);
}The match expression is a powerful tool in Rust that allows you to compare a value against a set of patterns and execute code based on the pattern that matches.
Example:
let number = 3;
match number {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Something else"),
}In this example, the _ pattern is a catch-all, meaning it will match any value not explicitly matched by the other arms.
You can also match multiple patterns with a single arm:
let number = 1;
match number {
1 | 2 => println!("One or Two"),
_ => println!("Something else"),
}You can match a range of values:
let number = 6;
match number {
1..=5 => println!("Between 1 and 5"),
_ => println!("Outside the range"),
}This module covers Rust's control flow mechanisms, such as conditionals, loops, and pattern matching using match. These tools are essential for handling logic in your programs.
Structs are custom data types that let you create complex types by grouping together different data types. They are similar to classes in other programming languages.
// Define a struct
struct Person {
name: String,
age: u32,
}
// Create an instance of the struct
fn main() {
let person = Person {
name: String::from("Alice"),
age: 30,
};
println!("Name: {}, Age: {}", person.name, person.age);
}Enums allow you to define a type that can be one of several variants. This is useful for representing data that can take on a limited set of values.
// Define an enum
enum Direction {
Up,
Down,
Left,
Right,
}
// Use the enum
fn move_player(direction: Direction) {
match direction {
Direction::Up => println!("Moving up!"),
Direction::Down => println!("Moving down!"),
Direction::Left => println!("Moving left!"),
Direction::Right => println!("Moving right!"),
}
}
fn main() {
let direction = Direction::Up;
move_player(direction);
}The Option type is used to represent a value that may or may not be present. It is a powerful way to handle nullable values.
// Define a function that returns an Option
fn find_item(index: usize) -> Option<&'static str> {
let items = ["apple", "banana", "cherry"];
if index < items.len() {
Some(items[index])
} else {
None
}
}
fn main() {
match find_item(1) {
Some(item) => println!("Found: {}", item),
None => println!("Item not found."),
}
}Arrays in Rust are fixed-size collections of elements of the same type. The size of an array is determined at compile time.
// Define an array
fn main() {
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
println!("First number: {}", numbers[0]);
}Vectors are similar to arrays, but they can change size at runtime. They are more flexible than arrays and are a common choice for collections of data.
// Define a vector
fn main() {
let mut numbers: Vec<i32> = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
for number in &numbers {
println!("{}", number);
}
}Conclusion
In this module, we've covered advanced data types in Rust, including structs, enums, Option<T>, arrays, and vectors. Understanding these concepts is crucial for effective Rust programming.
String slicing allows you to reference a part of a string. This is useful when you only need a portion of the string.
fn main() {
let my_string = String::from("Hello, Rust!");
let slice = &my_string[0..5]; // Slicing the first 5 characters
println!("Slice: {}", slice); // Output: Hello
}You can change and manipulate strings in various ways, like adding or removing characters.
fn main() {
let mut my_string = String::from("Hello");
my_string.push_str(", Rust!"); // Adding text
println!("{}", my_string); // Output: Hello, Rust!
let new_string = my_string.replace("Rust", "World"); // Replacing text
println!("{}", new_string); // Output: Hello, World!
}Tuples are a way to group different types of data together. They can hold a fixed number of values.
Copy code
fn main() {
let my_tuple = (10, 20.5, "Hello");
println!("First: {}, Second: {}, Third: {}", my_tuple.0, my_tuple.1, my_tuple.2);
}Structs, as mentioned in Module 4, are custom data types that allow you to group related data together.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 10, y: 20 };
println!("Point is at ({}, {})", p.x, p.y);
}- Functions and Closures
Functions are blocks of code that perform a specific task. You can reuse them throughout your program.
fn greet(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
greet("Alice");
}Closures are similar to functions but can capture variables from their surrounding environment.
fn main() {
let x = 5;
let add = |y| y + x; // Closure that captures `x`
println!("5 + 5 = {}", add(5)); // Output: 10
}Conclusion In this module, we've covered string slicing, string manipulation, tuples, structs, functions, and closures. These concepts are fundamental for writing effective Rust programs.
Ownership is a key feature of Rust that ensures memory safety. Every value in Rust has a single owner, which is the variable that holds it. When the owner goes out of scope, the value is dropped and the memory is freed.
fn main() {
let s = String::from("Hello"); // `s` owns the string
println!("{}", s); // Use the string
// `s` goes out of scope here, and the memory is freed
}Borrowing allows you to reference a value without taking ownership of it. This is done using references. You can borrow values as immutable or mutable references.
Immutable Borrowing
fn main() {
let s = String::from("Hello");
let r = &s; // Immutable borrow
println!("{}", r); // Use the borrowed value
}
Mutable Borrowing
You can have one mutable reference or multiple immutable references, but not both at the same time.
```rust
fn main() {
let mut s = String::from("Hello");
let r = &mut s; // Mutable borrow
r.push_str(", Rust!"); // Modify the borrowed value
println!("{}", r); // Output: Hello, Rust!
}Crates are packages of Rust code. You can create your own crates or use existing ones from the Rust community.
Creating a Crate To create a new crate, use Cargo, Rust’s package manager and build system. In your terminal, run:
cargo new my_crate
cd my_crateThis will create a new directory with a basic project structure.
To use external crates, you need to add them to your Cargo.toml file. For example, to use the rand crate for random number generation, add this line under [dependencies]:
[dependencies]
rand = "0.8" # Check for the latest versionThen, run:
cargo buildOnce you have added a crate, you can use it in your code. For example:
use rand::Rng; // Import the `Rng` trait
fn main() {
let random_number = rand::thread_rng().gen_range(1..101); // Generate a random number
println!("Random number: {}", random_number);
}In this module, we've explored ownership, borrowing, and crates in Rust. Understanding these concepts is essential for managing memory safely and using external libraries effectively.