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 | shCheck Installation
Verify that Rust and Cargo (the package manager) are installed correctly.
rustc --version
cargo --versionUpdate Rust
To keep Rust updated:
rustup updateInstall Additional Tools
Rust supports linters and formatters like rustfmt and clippy.
rustup component add rustfmt
rustup component add clippyBasic 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_nameThe 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 binaryVariables 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 variableConstant 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; // MutableBasic 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: 6Data 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: HelloStructs
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 JohnOwnership 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 ownershipLifetimes
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 testCargo and Dependency Management
Cargo.toml
The Cargo.toml file manages dependencies and project configurations.
[dependencies]
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }