Rust
Introduction to
What is Rust?
A modern, multi-paradigm programming language.
-
Memory Safety: Ensures safety without a garbage collector, minimizing memory-related errors.
-
Concurrency Made Safe: Its ownership model prevents data races in concurrent code.
- Performance: Offers C/C++ level performance; ideal for system-level and performance-critical applications.
-
Modern Tooling: Features like Cargo (package manager) and detailed compiler error messages enhance developer productivity.
-
Robust Ecosystem: Growing library support for diverse development needs, from web to embedded systems.
-
Cross-Platform & Integrable: Compatible with various platforms and can be integrated with other languages.
- Industry Adoption: Used by major companies, indicating reliability and effectiveness in real-world applications.
Hello, Rust!
# follow prompt to install rust toolchain using rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# check installation
rustc --version
# should print soemthing like "rustc 1.74.0 (79e9716c9 2023-11-13)""fn main() {
println!("Hello, Rust!");
}Create a file `hello.rs`
Installation
Compile and run
rustc hello.rs
./hello
# Hello, Rust!Hello, Cargo!
cargo new helloCreating a new package
cargo run # compile and run
cargo build # build
cargo test # run test
cargo add rand # add a crate

Variables
fn main() {
let first_name = "Salama"; // &str
let last_name: = "Ashoush"; // &str
println!("Hello, {} {}!", first_name, last_name);
}Inference
Variables
fn main() {
let first_name: &str = "Salama";
let last_name: &str = "Ashoush";
println!("Hello, {} {}!", first_name, last_name);
}Type annotation
Variables
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}
println!("The value of x is: {x}");
}Shadowing
Variables
fn main() {
let mut first_name: &str = "Salama";
let last_name: &str = "Ashoush";
println!("Hello, {} {}!", first_name, last_name);
first_name = "Ramadan";
println!("Hello once more, {}!", first_name);
}Mutable variables
Variables
fn main() {
// constants should shout and init with type annotation
const ARE_YOU_COOL: bool = true;
println!("Are you cool?, {}!", ARE_YOU_COOL);
}Constants
Primitive types
numbers

Primitive types
floats
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}bool
fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}Primitive types
Numeric operators
fn main() {
// addition
let sum = 5 + 10;
// subtraction
let difference = 95.5 - 4.3;
// multiplication
let product = 4 * 30;
// division
let quotient = 56.7 / 32.2;
let truncated = -5 / 3; // Results in -1
// remainder
let remainder = 43 % 5;
}Primitive types
char
fn main() {
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '😻';
}&str
fn main() {
let first_name = "Salama"; // &str (string slice)
// you can do lots of stuff with it, eg
first_name.len(); // 6
first_name.is_empty(); // false
first_name.contains("S"); // true
first_name.replace("S", "A"); // "Alama"
first_name.to_uppercase(); // "SALAMA"
first_name.to_lowercase(); // "salama"
first_name.chars(); // ['S', 'a', 'l', 'a', 'm', 'a'] (iterator)
first_name.split(" "); // ["Salama"]
}Primitive types
Tuple
fn main() {
let tup = (500, 6.4, 1);
// we can get the values by destructing
let (x, y, z) = tup;
println!("The value of y is: {y}");
// or by index
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;
// Tuples can include mixed types
let person = ("Salama", 30, true);
}
Primitive types
Arrays
fn main() {
// array cant include mixed types
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
// using destructing
// you must match all the array
let [first, second, _3, _4, _5] = a;
// or use the .. to ignore the rest
let [first,second, ..] = a;
// using type annotation
let a: [i32; 5] = [1, 2, 3, 4, 5];
// init 3 element array with the same value
let a = [3; 5];
a.len(); // 3
// and many more
}
Functions
fn say_hello() {
println!("Hello, world!");
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn early_returns() {
if condition {
return something;
}
println!("otherwise!");
}
fn main() {
let a = 1;
let b = 2;
let c = add(a, b);
println!("{} + {} = {}", a, b, c);
say_hello();
early_returns();
}if
fn main() {
let number = 3;
// if expression
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
// this will error -> if coditions must evaluate to bool
if number {
println!("number was three");
}
// if else if
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
// after all if is an expression
let number = if condition { 5 } else { 6 };
// this would error?
let number = if condition { 5 } else { "six" };
}looping
fn main() {
// infinite loop
loop {
println!("again!");
}
// loop with break
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // break the loop and return the value
}
};
println!("The result is {}", result);
// for loop
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is {}", element);
}
// for loop with range
for number in 1..4 {
println!("{}!", number);
}
// while loop
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
}
looping
fn main() {
// infinite loop
loop {
println!("again!");
}
// loop with break
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // break the loop and return the value
}
};
println!("The result is {}", result);
// for loop
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is {}", element);
}
// for loop with range
for number in 1..4 {
println!("{}!", number);
}
// while loop
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
}
Vectors
fn main() {
// vectors can only store values of the same type and are allocated on the heap
let mut v: Vec<i32> = Vec::new();
v.push(1);
// vector macro
let mut v2 = vec![1, 2, 3];
// length and capacity
v2.len(); // 3
v2.capacity(); // 3 (default)
v2.push(4);
v2.len(); // 4
v2.capacity(); // 6 (doubled)
// reading elements
let third: i32 = v[2];
println!("The third element is {}", third);
// reading elements with get
let third = v.get(2); // Some(&3)
// reading elements out of bounds
let does_not_exist = &v[100]; // panic
let does_not_exist = v.get(100); // None
// iterating over values
for i in v {
println!("{}", i);
}
// iterating over mutable references
for i in &mut v {
*i += 50;
}
}
Rustlings time
Memory allocation
- Automatic Management: Memory is managed automatically; variables are popped off when out of scope.
- Fast Access: Quick memory access, suitable for fixed-size data.
- Fixed Size at Compile Time: Requires knowing the size of data at compile time.
- Limited Capacity: Has size limits; excessive allocation can lead to stack overflow.
Stack
Memory allocation
-
Manual Management: Memory must be explicitly allocated and deallocated, typically via smart pointers like
Box. - Slower Access: Involves pointer dereferencing, slower than stack access.
- Dynamic Size: Used for data that grows at runtime or has an unknown size at compile time.
- Ownership System: Rust uses ownership, borrowing, and lifetimes to manage heap memory safely, preventing leaks and dangling pointers.
Heap
Ownership
fn main() {
// ownership rules
// 1. Each value in Rust has a variable that’s called its owner.
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() appends a literal to a String
// 2. There can only be one owner at a time.
// moving ownership to another variable
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1); // error
// cloning the heap data (deep copy)
let s1 = String::from("hello");
let s2 = s1.clone();
// copy types
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
// 3. When a variable goes out of scope its value will be dropped
let s = String::from("hello");
takes_ownership(s);
println!("{}", s); // error
}
fn takes_ownership(some_string: String) {
// some_string comes into scope here
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing memory is freed.
Borrowing
fn main() {
// 1. You can have as many immutable references as you want at any given time.
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2);
// 2. You can have exactly one mutable reference.
let r3 = &mut s; // error
println!("{}", r3);
let r4 = &mut s; // error
// 3. You can't have a mutable reference while you have an immutable one.
let r5 = &s; // error you can't borrow `s` as mutable because it is also borrowed as immutable
println!("{}", r5);
r4.push_str(" world");
// Rust is smart enough to know that this is not a problem
let mut s2 = String::from("hello");
let r1 = &s2;
println!("{} and {}", r1, r2);
// r1 is no longer used after this point
let r2 = &mut s2;
println!("{}", r2);
r2.push_str(" world");
// r2 is no longer used after this point
let r3 = &s2;
println!("{}", r3);
}
Structs
struct User {
username: String,
email: String,
}
// tuple struct
struct Point(i32, i32, i32);
// unit struct
struct Unit
fn main() {
// create instance
let mut user = User {
username: String::from("user"),
email: String::from("ss@ss.com"),
};
println!("username: {}", user.username);
println!("email: {}", user.get_username());
// update
user.username = String::from("user_new");
println!("username: {}", user.username);
// update from other instance
let user2 = User {
username: String::from("user2"),
..user
};
let p = Point(1, 2, 3);
println!("p.0: {}", p.0);
let unit_struct = UnitStruct;
}Impl
// implementation block
impl User {
// constructor function (associated function) (no self)
fn new(username: String, email: String) -> Self {
Self {
username,
email,
sign_in_count: 1,
active: true,
}
}
// method (self is a reference to the instance of the struct)
fn get_username(&self) -> &String {
&self.username
}
// method (self is a mutable reference to the instance of the struct)
fn set_username(&mut self, username: String) {
self.username = username;
}
}
fn main() {
// calling associated function with ::
let mut user3 = User::new(String::from("user3"), String::from("ss@ss.com"));
// calling method with .
println!("username: {}", user3.get_username());
// calling method with . with mutable reference and passing String
user3.set_username(String::from("user3_new"));
println!("username: {}", user3.get_username());
}
Impl
// implementation block
impl User {
// constructor function (associated function) (no self)
fn new(username: String, email: String) -> Self {
Self {
username,
email,
sign_in_count: 1,
active: true,
}
}
// method (self is a reference to the instance of the struct)
fn get_username(&self) -> &String {
&self.username
}
// method (self is a mutable reference to the instance of the struct)
fn set_username(&mut self, username: String) {
self.username = username;
}
}
fn main() {
// calling associated function with ::
let mut user3 = User::new(String::from("user3"), String::from("ss@ss.com"));
// calling method with .
println!("username: {}", user3.get_username());
// calling method with . with mutable reference and passing String
user3.set_username(String::from("user3_new"));
println!("username: {}", user3.get_username());
}
Enums
// enums are types which have a few definite values
enum Movement {
Up,
Down,
}
// with values
enum Movement2 {
Up(u8),
Down(u8),
}
// impl block
impl Movement2 {
fn match_movement(&self) {
match self {
Movement2::Up(1) => println!("Up 1"),
Movement2::Down(1) => println!("Down 1"),
_ => println!("No match"),
}
}
}
fn main() {
// without values
let move_up = Movement::Up;
match move_up {
Movement::Up => println!("Moving up"),
Movement::Down => println!("Moving down"),
}
// with values
let move_down = Movement2::Down(1);
move_down.match_movement();
}Traits
struct Dog { name: String, age: i8 }
struct Cat { lives: i8 } // No name needed, cats won't respond anyway.
trait Pet {
fn talk(&self) -> String;
fn greet(&self) {
println!("Oh you're a cutie! What's your name? {}", self.talk());
}
}
impl Pet for Dog {
fn talk(&self) -> String { format!("Woof, my name is {}!", self.name) }
}
impl Pet for Cat {
fn talk(&self) -> String { String::from("Miau!") }
}
fn main() {
let captain_floof = Cat { lives: 9 };
let fido = Dog { name: String::from("Fido"), age: 5 };
captain_floof.greet();
fido.greet();
}Deriving
#[derive(Debug, Clone, Default)]
struct Player {
name: String,
strength: u8,
hit_points: u8,
}
fn main() {
let p1 = Player::default(); // Default trait adds `default` constructor.
let mut p2 = p1.clone(); // Clone trait adds `clone` method.
p2.name = String::from("EldurScrollz");
// Debug trait adds support for printing with `{:?}`.
println!("{:?} vs. {:?}", p1, p2);
}Option
fn main() {
let name = "Löwe 老虎 Léopard Gepardi";
let mut position: Option<usize> = name.find('é');
println!("find returned {position:?}");
assert_eq!(position.unwrap(), 14); // will get the wrapped value or panic
position = name.find('Z');
println!("find returned {:?}", position);
assert_eq!(position.expect("Character not found"), 0); // expect is the same but with custom error
// you can do pattern matching on it
let l = match name.find('l') {
Some(position) => position,
None => 0,
};
}Result
use std::fs::File;
use std::io::Read;
fn main() {
let file: Result<File, std::io::Error> = File::open("diary.txt");
match file {
Ok(mut file) => {
let mut contents = String::new();
if let Ok(bytes) = file.read_to_string(&mut contents) {
println!("Dear diary: {contents} ({bytes} bytes)");
} else {
println!("Could not read file content");
}
},
Err(err) => {
println!("The diary could not be opened: {err}");
}
}
}Hashmaps
use std::collections::HashMap;
fn main() {
let mut page_counts = HashMap::new();
page_counts.insert("Adventures of Huckleberry Finn".to_string(), 207);
page_counts.insert("Grimms' Fairy Tales".to_string(), 751);
page_counts.insert("Pride and Prejudice".to_string(), 303);
if !page_counts.contains_key("Les Misérables") {
println!("We know about {} books, but not Les Misérables.",
page_counts.len());
}
for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
match page_counts.get(book) {
Some(count) => println!("{book}: {count} pages"),
None => println!("{book} is unknown.")
}
}
// Use the .entry() method to insert a value if nothing is found.
for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
let page_count: &mut i32 = page_counts.entry(book.to_string()).or_insert(0);
*page_count += 1;
}
println!("{page_counts:#?}");
// there are no hashmap! like vec! but you can create one from any iterator
let page_counts = HashMap::from([
("Harry Potter and the Sorcerer's Stone".to_string(), 336),
("The Hunger Games".to_string(), 374),
]);
}Generics
/// Pick `even` or `odd` depending on the value of `n`.
fn pick<T>(n: i32, even: T, odd: T) -> T {
if n % 2 == 0 {
even
} else {
odd
}
}
fn main() {
println!("picked a number: {:?}", pick(97, 222, 333));
println!("picked a tuple: {:?}", pick(28, ("dog", 1), ("cat", 2)));
}Generics
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn coords(&self) -> (&T, &T) {
(&self.x, &self.y)
}
// fn set_x(&mut self, x: T)
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
println!("{integer:?} and {float:?}");
println!("coords: {:?}", integer.coords());
}Generics
fn duplicate<T: Clone>(a: T) -> (T, T) {
(a.clone(), a.clone())
}
// with where
fn duplicate<T>(a: T) -> (T, T)
where
T: Clone,
{
(a.clone(), a.clone())
}
fn main() {
let foo = String::from("foo");
let pair = duplicate(foo);
println!("{pair:?}");
}
Trait bounds
Generics
// Syntactic sugar for:
// fn add_42_millions<T: Into<i32>>(x: T) -> i32 {
fn add_42_millions(x: impl Into<i32>) -> i32 {
x.into() + 42_000_000
}
fn pair_of(x: u32) -> impl std::fmt::Debug {
(x + 1, x - 1)
}
fn pair_of<T: Debug>(x: u32) -> T {
(x + 1, x - 1)
}
fn main() {
let many = add_42_millions(42_i8);
println!("{many}");
let many_more = add_42_millions(10_000_000);
println!("{many_more}");
let debuggable = pair_of(27);
println!("debuggable: {debuggable:?}");
}Trait bounds using impl
Pattern matching
fn main() {
let input = 'x';
match input {
'q' => println!("Quitting"),
'a' | 's' | 'w' | 'd' => println!("Moving around"),
'0'..='9' => println!("Number input"),
key if key.is_lowercase() => println!("Lowercase: {key}"),
_ => println!("Something else"),
}
}
Pattern matching
fn main() {
describe_point((1, 0));
}
fn describe_point(point: (i32, i32)) {
match point {
(0, _) => println!("on Y axis"),
(_, 0) => println!("on X axis"),
(x, _) if x < 0 => println!("left of Y axis"),
(_, y) if y < 0 => println!("below X axis"),
_ => println!("first quadrant"),
}
}Pattern matching
#[rustfmt::skip]
fn main() {
let triple = [0, -2, 3];
println!("Tell me about {triple:?}");
match triple {
[0, y, z] => println!("First is 0, y = {y}, and z = {z}"),
[1, ..] => println!("First is 1 and the rest were ignored"),
[.., b] => // tails ignore head,
[a@..,b] => // tail aliasing the head,
_ => println!("All elements were ignored"),
}
}Pattern matching
fn sleep_for(secs: f32) {
let dur = if let Ok(dur) = std::time::Duration::try_from_secs_f32(secs) {
dur
} else {
std::time::Duration::from_millis(500)
};
std::thread::sleep(dur);
println!("slept for {:?}", dur);
}
fn main() {
sleep_for(-10.0);
sleep_for(0.8);
}Pattern matching
fn sleep_for(secs: f32) {
let wahatever = if let Ok(d) = std::time::Duration::try_from_secs_f32(secs) {
d
} else {
std::time::Duration::from_millis(500)
};
std::thread::sleep(whatever);
println!("slept for {:?}", whatever);
}
fn main() {
sleep_for(-10.0);
sleep_for(0.8);
}Pattern matching
fn sleep_for(secs: f32) {
let wahatever = if let Ok(d) = std::time::Duration::try_from_secs_f32(secs) {
d
} else {
std::time::Duration::from_millis(500)
};
std::thread::sleep(whatever);
println!("slept for {:?}", whatever);
}
fn main() {
sleep_for(-10.0);
sleep_for(0.8);
}Pattern matching
fn sleep_for(secs: f32) {
let dur = if let Ok(dur) = std::time::Duration::try_from_secs_f32(secs) {
dur
} else {
std::time::Duration::from_millis(500)
};
std::thread::sleep(dur);
println!("slept for {:?}", dur);
}
fn main() {
sleep_for(-10.0);
sleep_for(0.8);
}Modules
mod foo {
pub fn do_something() {
println!("In the foo module");
}
}
mod bar {
pub fn do_something() {
println!("In the bar module");
}
}
fn main() {
foo::do_something();
bar::do_something();
}Modules
mod foo {
pub fn do_something() {
println!("In the foo module");
}
}
mod bar {
pub fn do_something() {
println!("In the bar module");
}
}
fn main() {
foo::do_something();
bar::do_something();
}Rustlings
Project
- Bevy (for building games) https://arewegameyet.rs/
- Actix, Sqlx (for building web API) https://www.arewewebyet.org/
- Leptos, dioxus, yew (for frontend apps)
- Tauri, egui (for desktop applications) https://areweguiyet.com/
- Clap, ratatui (for CLI apps) https://rust-cli.github.io/book/index.html
https://wiki.mozilla.org/Areweyet
Resources
- https://doc.rust-lang.org/book/title-page.html
- https://google.github.io/comprehensive-rust/index.html
- https://doc.rust-lang.org/rust-by-example/
- https://practice.rs/why-exercise.html
Introduction to Rust
By Salama Ashoush
Introduction to Rust
- 143