Rust is a systems programming language developed by Mozilla. It is known for its safety, performance, and concurrency.
Installation and Setup
Install Rust
Rust uses a tool called rustup
for installation and version management.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Check Installation
Verify that Rust and Cargo (the package manager) are installed correctly.
rustc --version
cargo --version
Update Rust
To keep Rust updated:
rustup update
Install Additional Tools
Rust supports linters and formatters like rustfmt
and clippy
.
rustup component add rustfmt
rustup component add clippy
Basic Structure of a Program
Hello, World
The simplest program in Rust prints “Hello, World” to the console.
fn main() {
println!("Hello, World!");
}
Basic Project Structure
Rust uses Cargo
to manage projects. Create a new project:
cargo new project_name
The main file will be in src/main.rs
, and the project will include a Cargo.toml
to define dependencies and configurations.
Compile and Run
cargo build # Just compiles
cargo run # Compiles and runs
cargo check # Checks for compilation errors without generating binary
Variables and Data Types
Variable Declaration
let
declares a variable. By default, they are immutable, but mut
can be added to make them mutable.
let name = "John"; // Immutable variable
let mut age = 30; // Mutable variable
Constant Declaration
const
declares a constant. It must have an explicit type, and its value cannot change.
const PI: f64 = 3.14159;
Immutability
By default, variables in Rust are immutable. To make them mutable, mut
is used.
let x = 5; // Immutable
let mut y = 10; // Mutable
Basic Types
Rust is a strongly typed language. Some of the basic types are:
- Integers:
i8
,i16
,i32
,i64
,i128
,u8
,u16
,u32
,u64
,u128
- Floating Points:
f32
,f64
- Booleans:
bool
- Characters:
char
let number: i32 = 42;
let name: &str = "Rust";
let floating: f64 = 3.14;
let is_true: bool = true;
let character: char = 'R';
String
String
Strings in Rust (String
) are dynamically managed and can be modified.
let mut greeting = String::from("Hello");
greeting.push_str(", world!");
str
Static string.
let static_text: &str = "Hello, world!";
Control Flow
Conditionals
if-else
The if
conditional in Rust is used similarly to other languages.
if x > 5 {
println!("x is greater than 5");
} else {
println!("x is less than or equal to 5");
}
match
Allows pattern matching.
let number = 5;
match number {
1 => println!("It's one"),
2..=5 => println!("It's between 2 and 5"),
_ => println!("Another number"),
}
Loops
for Loop
Loop to iterate over ranges and collections.
for i in 1..5 {
println!("i: {}", i); // i takes values from 1 to 4
}
while Loop
Loop that runs while a condition is met.
let mut counter = 0;
while counter < 5 {
println!("counter: {}", counter);
counter += 1;
}
loop
Infinite loop.
loop {
println!("This loop is infinite");
break; // Ends the loop
}
Functions
Function Definition
Functions in Rust are declared with fn
and can return values. The types of parameters and return types must be specified explicitly.
fn sum(a: i32, b: i32) -> i32 {
a + b
}
let result = sum(5, 3);
Closures
Closures in Rust are anonymous functions that can capture variables from their surrounding environment.
let add_one = |x| x + 1;
let result = add_one(5);
println!("Result: {}", result); // Result: 6
Data Structures
Tuples
Tuples are collections of elements of different types with a fixed size.
let tuple: (i32, f64, &str) = (42, 3.14, "Hello");
let (x, y, z) = tuple;
println!("x: {}, y: {}, z: {}", x, y, z); // x: 42, y: 3.14, z: Hello
Structs
Structs allow you to create custom data types with multiple fields.
struct Person {
name: String,
age: i32,
}
let john = Person {
name: String::from("John"),
age: 30,
};
println!("Name: {}, Age: {}", john.name, john.age);
Vectors
Vectors are collections of elements of the same type that can grow or shrink in size.
let mut v: Vec<i32> = Vec::new();
v.push(1);
v.push(2);
v.push(3);
HashMap
HashMaps store key-value pairs.
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert(String::from("key"), 10);
Traits
Traits in Rust define common behaviors that a type can implement. It is similar to interfaces in other languages.
trait Greetable {
fn greet(&self) -> String;
}
struct Person {
name: String,
}
impl Greetable for Person {
fn greet(&self) -> String {
format!("Hello, I am {}", self.name)
}
}
let john = Person { name: String::from("John") };
println!("{}", john.greet()); // Hello, I am John
Ownership and References
Rust manages memory with an ownership system that ensures safety at compile time.
Ownership Rules
- Each value in Rust has a unique owner.
- When the owner goes out of scope, the value is dropped.
- References to the data can be passed, but they must comply with borrowing and lifetime rules.
Move Data
Moving transfers the ownership of the value.
let s1 = String::from("Hello");
let s2 = s1; // `s1` is no longer valid, ownership passes to `s2`
Clone Data
If you need to copy the data, clone
is used.
let s1 = String::from("Hello");
let s2 = s1.clone(); // Deep copy of `s1`
References and Borrowing
Rust ensures memory safety through the Ownership system. Each value in Rust has an owner, and there can only be one owner at a time.
To borrow a value without transferring ownership, references are used.
fn print(s: &String) {
println!("{}", s);
}
let string = String::from("Hello");
print(&string); // Passes a reference, does not transfer ownership
Lifetimes
The concept of lifetimes in Rust ensures that references do not outlive the data they point to.
fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
Error Handling
Rust handles errors explicitly through the Result
type and the Option
type.
Result
Result
is used for operations that can fail. It has two variants: Ok
and Err
.
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
Option
Option
is used for values that can be null (None
).
fn find_value(vec: &Vec<i32>, x: i32) -> Option<usize> {
vec.iter().position(|&val| val == x)
}
Concurrency in Rust
Rust offers safe concurrency with threads and allows using advanced techniques like channels and locks to avoid race conditions.
Threads
Rust uses the std::thread
package to create and manage threads.
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..5 {
println!("Hello from the thread");
thread::sleep(Duration::from_millis(1));
}
});
handle.join().unwrap();
}
Messaging Between Threads with Channels
Rust provides channels (channels
) for communication between threads.
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let value = String::from("Message");
tx.send(value).unwrap();
});
let received = rx.recv().unwrap();
println!("Received: {}", received);
}
Macros in Rust
Rust has support for macros that allow generating code at compile time.
Define Macros
macro_rules! my_macro {
($val:expr) => {
println!("The value is: {}", $val);
};
}
my_macro!(10);
Building and Testing in Rust
Rust facilitates development through integrated testing and conditional compilation.
Unit Tests
#[cfg(test)]
mod tests {
#[test]
fn basic_test() {
assert_eq!(2 + 2, 4);
}
}
Run Tests
cargo test
Cargo and Dependency Management
Cargo.toml
The Cargo.toml
file manages dependencies and project configurations.
[dependencies]
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }