Rust Programming
May 12, 2025 Dinesh MNRust is a fast and memory-safe systems programming language designed for performance and reliability. It uses ownership and borrowing principles to eliminate memory errors without a garbage collector. Rust is statically typed and compiled, ensuring high efficiency and safety in production.
Table of Contents
Check out my github repository for resource(code)
Basic Concepts
Variables and Mutability
let greet = String::from("Hello"); // Immutable by default
let mut s = String::from("Hello"); // Mutable with 'mut' keyword
s.push_str("SUP"); // Modifying mutable string
Example from main() function:
fn letn() {
let greet = String::from("Hello"); // heap memory of string
println!("greet: {}, Length: {}, slice: {}",
greet, greet.len(), &greet[0..2]);
let num = true;
if num {
println!("The number is true");
} else {
println!("The number is false");
}
for i in 1..10 {
print!("{} ", i);
}
}
Functions
// Function with parameters and return type
fn sum(a: i32, b: i32) -> i32 {
let c = a + b;
return c;
}
// Function with no return value
fn takes_ownership(s: String) {
println!("{}", s);
}
// Function that returns the passed parameter
fn takes_ownership_and_gives_back(s: String) -> String {
println!("{}", s);
s // Return value without "return" keyword (last expression)
}
Basic Data Types
- Integers:
i32
,u32
- Booleans:
bool
- Strings:
String
,&str
- Arrays and Slices
- Tuples
Main Function Example
fn main() {
letn();
heap_str();
let x = 10;
let y = 20;
println!("Sum is {}", sum(x, y));
let my_string = String::from("Hello World");
takes_ownership(my_string);
// println!("{}", my_string); // this line won't work as my_string is no longer valid
let my_string = String::from("Hello World");
let my_string = takes_ownership_and_gives_back(my_string);
println!("{}", my_string);
refr();
let mut sting = String::from("Helloborrw"); // mutable reference, borrows the ownership
mutborrw(&mut sting);
// Struct usage example
let user1 = User {
name: String::from("Dinesh"),
age: 20,
active: true,
};
println!("Name: {}, Age: {}, Active: {}", user1.name, user1.age, user1.active);
// Method call example
let rect1 = Rect {
width: 20,
height: 30,
};
println!("Area of rectangle is: {}", rect1.area());
// Enum usage example
let dir = Direction::North;
move_dir(dir);
// Option example
let input = String::from("Hello SuperMan");
match find_first_a(input) {
Some(ind) => println!("First a is at index: {}", ind),
None => println!("No a found"),
}
}
Memory Management
Stack vs Heap
-
Stack: Fixed size, fast access, follows LIFO
- Primitive types: integers, booleans
- Function calls and local variables
-
Heap: Dynamic size, slower access
-
Complex types: String, Vec, etc.
-
When size is unknown at compile time or might change
-
Borrowing and References
// Immutable reference
let s = String::from("Hello");
let s1 = &s; // s1 is a reference to s
println!("{}", s1);
println!("{}", s); // s is still valid
// Mutable reference
let mut sting = String::from("Hello");
mutborrw(&mut sting); // Passing mutable reference
fn mutborrw(st: &mut String) {
st.push_str("Hello");
}
Complete example from borrow_refference.rs:
fn main() {
// Ownership and borrowing
let s1 = String::from("Hello");
let s2 = s1;
println!("s2: {}", s2);
// println!("s1: {}", s1); // Can't be printed because s1 is borrowed by s2
// Immutable reference
let s3 = String::from("Borrow-ref");
let s4 = &s3;
println!("s3: {}", s3);
println!("s4: {}", s4); // Can be printed because it just carries the reference
// Mutable reference
let mut s6 = String::from("Mut-refernece");
mut_ref(&mut s6);
println!("s6: Mut-ref: {}", s6);
}
fn mut_ref(s5: &mut String) {
s5.push_str(" Borrowing");
println!("s5: {}", s5);
}
Rules of References:
- At any time, you can have either:
- One mutable reference
- Any number of immutable references
- References must always be valid (no dangling references)
Function that demonstrates references:
fn refr() {
let s = String::from("Hello");
let s1 = &s; // s1 is a reference to s, valid as long as s is valid
println!("{}", s1);
println!("{}", s);
}
Data Structures
Structs
// Defining a struct
struct User {
name: String,
age: u32,
active: bool,
}
// Creating an instance
let user1 = User {
name: String::from("Dinesh"),
age: 20,
active: true,
};
// Accessing struct fields
println!("Name: {}, Age: {}", user1.name, user1.age);
Methods on Structs
struct Rect {
width: u32,
height: u32,
}
// Implementing methods for a struct
impl Rect {
fn area(&self) -> u32 {
self.width * self.height
}
}
// Using a method
let rect1 = Rect { width: 20, height: 30 };
println!("Area: {}", rect1.area());
Enums
// Defining an enum
enum Direction {
North,
South,
East,
West,
}
// Using an enum value
let dir = Direction::North;
Control Flow
Conditional Statements
let num = true;
if num {
println!("The number is true");
} else {
println!("The number is false");
}
Loops
// For loop with range
for i in 1..10 {
print!("{} ", i);
}
// Iteration with enumerate
for (ind, c) in s.chars().enumerate() {
// do something with index and character
}
Pattern Matching
// Match with enum
match direction {
Direction::North => println!("Moving North"),
Direction::South => println!("Moving South"),
Direction::East => println!("Moving East"),
Direction::West => println!("Moving West"),
}
// Match with Option
match find_first_a(input) {
Some(ind) => println!("First a is at index: {}", ind),
None => println!("No a found"),
}
Error Handling
Option Enum
Used for values that might be absent. The Option enum has two variants:Some(T)
containing a value, or None
representing no value.
// Returns Some(value) if found, None otherwise
fn find_first_a(s: String) -> Option<i32> {
for (ind, c) in s.chars().enumerate() {
if c == 'a' {
return Some(ind as i32);
}
}
return None;
}
// Using Option return value with pattern matching
match find_first_a(input) {
Some(ind) => println!("First a is at index: {}", ind),
None => println!("No a found"),
}
Complete example from option_enum.rs:
fn main() {
let s = String::from("Super_Man_D");
match find_first_a(s) {
Some(ind) => println!("First 'a' found at index {}", ind),
None => println!("No 'a' found"),
}
}
fn find_first_a(s: String) -> Option<i32> {
for (ind, c) in s.chars().enumerate() {
if c == 'a' {
return Some(ind as i32);
}
}
return None;
}
Result Type
Used for operations that might fail. Result is an enum with two variants:Ok(T)
for success and Err(E)
for failure.
// Example of a function that returns Result
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse::<i32>()
}
// Using Result with pattern matching
match parse_number("42") {
Ok(num) => println!("Successfully parsed: {}", num),
Err(e) => println!("Failed to parse: {}", e),
}
Collections
Vectors
// Creating and using vectors
let mut vec = Vec::new(); // Empty vector
vec.push(1); // Adding elements
vec.push(2);
// Vector initialization using macro
let number = vec![1, 2, 3];
// Filtering a vector
fn even_vec(vec: Vec<i32>) -> Vec<i32> {
let mut new_vec = Vec::new();
for val in vec {
if val % 2 == 0 {
new_vec.push(val);
}
}
return new_vec;
}
HashMaps
HashMaps store key-value pairs with efficient lookups.use std::collections::HashMap;
// Creating and using HashMaps
let mut users = HashMap::new();
users.insert(String::from("Dinesh"), 20);
users.insert(String::from("Sun"), 20);
// Accessing elements
let first_user = users.get("Sun");
match first_user {
Some(data) => println!("{}", data),
None => println!("User not found"),
}
// Common methods: insert(), get(), remove(), clear()
Complete example from Hashmaps.rs:
use std::collections::HashMap;
fn main() {
let mut users = HashMap::new();
users.insert(String::from("Dinesh"), 20);
users.insert(String::from("Sun"), 20);
let first_user = users.get("Sun");
match first_user {
Some(data) => println!("{}", data),
None => println!("User not found"),
}
}
// Methods: 1) Insert, 2) Get, 3) Remove, 4) Clear
// HashMap is a collection of key-value pairs
Creating a HashMap from a Vector:
use std::collections::HashMap;
fn group_by_values(vec: Vec<(String, i32)>) -> HashMap<String, i32> {
let mut hm = HashMap::new();
for (key, value) in vec {
hm.insert(key, value);
}
return hm;
}
fn main() {
let input_vec = vec![(String::from("Dinesh"), 20), (String::from("Sun"), 20)];
let hm = group_by_values(input_vec);
println!("{:?}", hm);
}
Strings and String Slices
// String: mutable, growable, owned
let mut name = String::from("Dinesh");
name.push_str(" MN");
// String slice: immutable, fixed size, borrowed
let string_slice = &name[0..6];
// String literal: immutable, fixed size, borrowed
let string_literal = "Dinesh MN";
// Finding first word
fn find_first_word(word: &String) -> &str {
let mut index = 0;
for (_, i) in word.chars().enumerate() {
if i == ' ' {
break;
}
index = index + 1;
}
return &word[0..index];
}
Complete example from str.rs:
fn main() {
let word = String::from("Hello World");
// Get first word using function
let word2 = find_first_word(&word);
println!("Word2: {}", word2);
// 3 types of commonly used string types:
// 1. String: mutable, growable, owned
// 2. String slice: immutable, fixed size, borrowed
// 3. String literal: immutable, fixed size, borrowed
// String literal is also &str but points directly to an address
let name = String::from("Dinesh MN");
let string_slice = &name[0..6]; // string slice
let string_literal = "Dinesh MN"; // string literal
println!("Name: {}", name);
println!("String slice: {}", string_slice);
println!("String literal: {}", string_literal);
}
fn find_first_word(word: &String) -> &str {
let mut index = 0;
for (_, i) in word.chars().enumerate() {
if i == ' ' {
break;
}
index = index + 1;
}
return &word[0..index];
}
Example from String_vs_slice.rs:
fn main() {
// String is mutable, growable, owned
println!("{}", "String is mutable, growable, owned");
// String slice is immutable, fixed size, borrowed
// Slice is immutable, fixed size, borrowed
println!("{}", "Slice is immutable, fixed size, borrowed");
// String manipulation
let mut name = String::from("Dinesh");
name.push_str(" MN");
println!("Name is {}", name);
name.replace_range(7..9, " ");
println!("Name is {}", name);
println!("Length: {} Capacity: {} Pointer: {:p}",
name.len(), name.capacity(), name.as_ptr());
// Finding first word
let name1 = String::from("Dinesh MN");
let ans = first_word(name1);
println!("First word is {}", ans);
}
fn first_word(str: String) -> String {
let mut ans = String::from("");
for i in str.chars() {
if i == ' ' {
break;
}
ans.push_str(&i.to_string()); // i isn't a string, so we convert it to string
}
return ans;
}
String length example:
fn main() {
let name = String::from("SUPER_MAN_D");
let len = get_string_len(name);
println!("Length of the string is {}", len);
}
fn get_string_len(s: String) -> usize {
return s.chars().count();
}
Advanced Topics
Iteration and Functional Programming
Iterators
Iterators provide a way to process sequences of elements.let v1 = vec![1, 2, 3];
// Different types of iterators:
let v1_iter = v1.iter(); // borrowed iterator (&T)
let mut v1_iter_mut = v1.iter_mut(); // mutable borrow iterator (&mut T)
let v1_into_iter = v1.into_iter(); // consuming iterator (T)
Complete example from iterator.rs:
fn main() {
let v1 = vec![1, 2, 3];
// Creating an immutable iterator
let mut v1_iter = v1.iter();
println!("{:?}", v1_iter);
// Manually advancing the iterator
let first = v1_iter.next();
let second = v1_iter.next();
let third = v1_iter.next();
let fourth = v1_iter.next(); // gives None
println!("First: {:?}, Second: {:?}, Third: {:?}, Fourth: {:?}",
first, second, third, fourth);
// Using while let with iterators
while let Some(val) = v1_iter.next() {
println!("Got {}", val);
}
// Using for loop with iterators
for i in v1.iter() {
println!("Got {}", i);
}
}
Iterator Methods
Iterators have many useful methods for functional-style programming.let v1 = vec![1, 2, 3];
// Consuming iterator: sum()
let sum: i32 = v1.iter().sum(); // consumes the iterator
// Transforming with map and collecting
let v2: Vec<i32> = v1.iter().map(|x| x + 1).collect();
// Iterating with into_iter (takes ownership)
let v1_into_iter = v1.into_iter();
println!("{:?}", v1_into_iter);
Complete example from iter.rs:
fn main() {
let v1 = vec![1, 2, 3];
// Using into_iter to take ownership
let v1_iter = v1.into_iter(); // takes ownership of v1 and moves it into the iterator
println!("{:?}", v1_iter);
// Consuming with sum()
let sum: i32 = v1_iter.sum(); // sum takes ownership of the iterator and consumes it
println!("Sum: {}", sum); // prints 6
// If it takes &self as an argument then it will not consume the iterator
// Recreate vector since v1 was moved
let v1 = vec![1, 2, 3];
// Map and collect
let iter2 = v1.into_iter().map(|x| x + 1);
let v2: Vec<i32> = iter2.collect();
println!("{:?}", v2); // prints the transformed vector [2, 3, 4]
}
Practical Examples
Fibonacci Sequence
Complete example from fibonacci.rs:fn main() {
println!("{}", fib(12));
}
fn fib(num: i32) -> i32 {
let mut first = 0;
let mut second = 1;
if num == 0 {
return first;
}
if num == 1 {
return second;
}
for _ in 0..(num - 1) {
let temp = first + second;
first = second;
second = temp;
}
return second;
}
String Processing
Vowel Counter Example
From vowels_or_not.rs:
fn main() {
let name = String::from("SUPER_MAN_D");
println!("number of vowels present in the name is {}", vowels(&name));
}
fn vowels(name: &str) -> i32 {
let vowels = ['a','e','i','o','u','A','E','I','O','U'];
let mut count = 0;
for (_, char) in name.chars().enumerate() {
if vowels.contains(&char) {
count += 1;
}
}
count
}
Even Number Check
From is_even.rs:
fn main() {
println!("{}", is_even(10));
}
fn is_even(num: i32) -> bool {
if num % 2 == 0 {
return true;
} else {
return false;
}
}
Collection Processing
Vector Filtering
fn even_vec(vec: Vec<i32>) -> Vec<i32> {
let mut new_vec = Vec::new();
for val in vec {
if val % 2 == 0 {
new_vec.push(val);
}
}
return new_vec;
}
Key Concepts from the Files
- Variable Binding & Mutability: Using
let
for immutable variables andlet mut
for mutable ones. - Ownership: Rust's unique approach to memory management without garbage collection.
- Borrowing: Using references (
&
and&mut
) to avoid ownership transfer. - Collections: Working with
Vec<T>
,HashMap<K, V>
, etc. - String Types: Understanding
String
vs string slices (&str
). - Structs & Enums: Custom data types for organizing related data.
- Pattern Matching: Using
match
for branching logic. - Option & Result: Handling absence of values and potential failures.
- Iterators: Processing sequences of elements.
- Functions: Writing and using functions with different parameter and return types.
Advanced Topics to Explore
- Traits and Generics: Enabling polymorphism and code reuse
- Lifetimes: Managing reference validity
- Multithreading: Safe concurrent programming
- Macros: Meta-programming in Rust
- Async/Await and Tokio: Asynchronous programming
- Futures: Representing asynchronous operations
- Package Management: Using Cargo and crates
- Smart Pointers: Types like Box, Rc, Arc, etc.
- Error Handling Strategies: Using ? operator, propagating errors
- FFI (Foreign Function Interface): Interoperating with C code
- Unsafe Rust: Working with unsafe blocks when necessary