version 1.0.
Table of Contents
Basic Types & Variables
Control Flow
References, Ownership, and Borrowing
Pattern Matching
Iterators
Error Handling
Generics, Traits, and Lifetimes
Functions, Function Pointers & Closures
Pointers
Packages, Crates, and Modules
YouTube Channel: https://www.youtube.com/c/LetsGetRust
String::from("Golang Dojo")
Basic Types & Variables ).or_insert(3);
bool - Boolean
Struct
Unsigned integers
// Definition
u8, u16, u32, u64, u128
struct User {
username: String,
Signed integers
active: bool,
i8, i16, i32, i64, i128
}
Floating point numbers
// Instantiation
f32, f64
let user1 = User {
username: String::from("bogdan"),
Platform specific integers
active: true,
usize - Unsigned integer. Same number of bits as the
};
platform's pointer type.
// Tuple struct
isize - Signed integer. Same number of bits as the
struct Color(i32, i32, i32);
platform's pointer type.
let black = Color(0, 0, 0);
char - Unicode scalar value
&str - String slice Enum
String - Owned string // Definition
enum Command {
Tuple Quit,
let coordinates = (82, 64); Move { x: i32, y: i32 },
let score = ("Team A", 12) Speak(String),
ChangeBGColor(i32, i32, i32),
}
Array & Slice
// Arrays must have a known length and all // Instantiation
elements must be initialized let msg1 = Command::Quit;
let array = [1, 2, 3, 4, 5]; let msg2 = Command::Move{ x: 1, y: 2 };
let array2 = [0; 3]; // [0, 0, 0] let msg3 = Command::Speak(
String::from("Hi")
// Unlike arrays the length of a slice is );
determined at runtime let msg4 = Command::ChangeBGColor(0, 0, 0);
let slice = &array[1 .. 3];
Constant
HashMap
const MAX_POINTS: u32 = 100_000;
use std::collections::HashMap;
Static variable
let mut subs = HashMap::new();
subs.insert(String::from("LGR"), 100000); // Unlike constants static variables are
// Insert key if it doesn't have a value // stored in a dedicated memory location
subs.entry( // and can be mutated.
static MAJOR_VERSION: u32 = 1; // This breaks the inner loop
static mut COUNTER: u32 = 0; break;
// This breaks the outer loop
break 'outer;
}
Mutability }
let mut x = 5; Returning from loops
x = 6; let mut counter = 0;
Shadowing let result = loop {
counter += 1;
let x = 5;
let x = x * 2; if counter == 10 {
break counter;
Type alias }
};
// `NanoSecond` is a new name for `u64`.
type NanoSecond = u64;
while and while let
Control Flow while n < 101 {
n += 1;
if and if let }
let num = Some(22);
let mut optional = Some(0);
if num.is_some() {
println!("number is: {}", num.unwrap()); while let Some(i) = optional {
} print!("{}", i);
}
// match pattern and assign variable
if let Some(i) = num { for loop
println!("number is: {}", i);
} for n in 1..101 {
println!("{}", n);
}
loop
let mut count = 0; let names = vec!["Bogdan", "Wallace"];
loop {
count += 1; for name in names.iter() {
if count == 5 { println!("{}", name);
break; // Exit loop }
}
} match
let optional = Some(0);
Nested loops & labels
'outer: loop { match optional {
'inner: loop { Some(i) => println!("{}", i),
None => println!("No value.") let s2 = s1.clone(); // Deep copy
}
// Valid because s1 isn't moved
println!("{}", s1);
References, Ownership, and
Borrowing Ownership and functions
Ownership rules fn main() {
1. Each value in Rust has a variable that’s called its let x = 5;
owner. takes_copy(x); // x is copied by value
2. There can only be one owner at a time.
3. When the owner goes out of scope, the value will let s = String::from("Let’s Get Rusty!");
be dropped. // s is moved into the function
takes_ownership(s);
Borrowing rules
1. At any given time, you can have either one // return value is moved into s1
mutable reference or any number of immutable let s1 = gives_ownership();
references.
2. References must always be valid. let s2 = String::from("LGR");
let s3 = takes_and_gives_back(s2);
Creating references }
let s1 = String::from("hello world!");
fn takes_copy(some_integer: i32) {
let s1_ref = &s1; // immutable reference
println!("{}", some_integer);
}
let mut s2 = String::from("hello");
let s2_ref = &mut s2; // mutable reference
fn takes_ownership(some_string: String) {
println!("{}", some_string);
s2_ref.push_str(" world!");
} // some_string goes out of scope and drop
is called. The backing memory is freed.
Copy, Move, and Clone
// Simple values which implement the Copy fn gives_ownership() -> String {
trait are copied by value let some_string = String::from("LGR");
let x = 5; some_string
let y = x; }
println!("{}", x); // x is still valid fn takes_and_gives_back(some_string:
String) -> String {
// The string is moved to s2 and s1 is some_string
invalidated }
let s1 = String::from("Let's Get Rusty!");
let s2 = s1; // Shallow copy a.k.a move
println!("{}", s1); // Error: s1 is invalid
let s1 = String::from("Let's Get Rusty!");
let shape = Shape::Circle(10);
match shape {
Shape::Rectangle { x, y } => //...
Shape::Circle(radius) => //...
}
Pattern Matching
Basics Ignoring values
let x = 5; struct SemVer(i32, i32, i32);
match x { let version = SemVer(1, 32, 2);
// matching literals
1 => println!("one"), match version {
// matching multiple patterns SemVer(major, _, _) => {
2 | 3 => println!("two or three"), println!("{}", major);
// matching ranges }
4..=9 => println!("within range"), }
// matching named variables
x => println!("{}", x), let numbers = (2, 4, 8, 16, 32);
// default case (ignores value)
_ => println!("default Case") match numbers {
} (first, .., last) => {
println!("{}, {}", first, last);
}
Destructuring }
struct Point {
x: i32, Match guards
y: i32,
} let num = Some(4);
let p = Point { x: 0, y: 7 }; match num {
Some(x) if x < 5 => println!("less than
match p { five: {}", x),
Point { x, y: 0 } => { Some(x) => println!("{}", x),
println!("{}" , x); None => (),
}, }
Point { x, y } => {
println!("{} {}" , x, y); @ bindings
}, struct User {
} id: i32
}
enum Shape {
Rectangle { width: i32, height: i32 }, let user = User { id: 5 };
Circle(i32),
} match user {
User {
id: id_variable @ 3..=7, }
} => println!("id: {}", id_variable), }
User { id: 10..=12 } => { }
println!("within range");
},
User { id } => println!("id: {}", id),
}
Error Handling
Iterators
Throw unrecoverable error
Usage
panic!("Critical error! Exiting!");
// Methods that consume iterators
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter(); Option enum
let total: i32 = v1_iter.sum(); fn getUserId(username: &str) -> Option<u32>
{
// Methods that produce new iterators if database.userExists(username) {
let v1: Vec<i32> = vec![1, 2, 3]; Some(database.getIdForUser(username))
let iter = v1.iter().map(|x| x + 1); }
// Turning iterators into a collection None
let v1: Vec<i32> = vec![1, 2, 3]; }
let v2: Vec<_> = v1.iter().map(|x| x +
1).collect();
Result enum
fn getUser(id: u32) -> Result<User, Error>
Implementing the Iterator trait
{
struct Counter { if isLoggedInAs(id) {
count: u32, Ok(getUserObject(id))
} }
impl Counter { Err(Error { msg: "not logged in" })
fn new() -> Counter { }
Counter { count: 0 }
}
? operator
}
fn getSalary(db: Database, id: i32) ->
impl Iterator for Counter { Option<u32> {
type Item = u32; db.getUser(id)?.getJob()?.salary;
}
fn next(&mut self) -> Option<Self::Item>
{ fn connect(db: Database) ->
if self.count < 5 { Result<Connection, Error> {
self.count += 1; let conn =
Some(self.count) db.getActiveInstance()?.connect()?;
} else { Ok(conn)
None }
struct CustomError;
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) ->
fmt::Result {
write!(f, "custom error message")
}
}
Combinators
Boxing errors
.map
use std::error;
let maybe_some_string =
Some(String::from("LGR"));
type Result<T> = std::result::Result<T,
Box<dyn error::Error>>;
let maybe_some_len =
maybe_some_string.map(|s| s.len());
Iterating over errors
struct Error { msg: String } Ignore failed items with filter_map()
struct User { name: String }
let strings = vec!["LGR", "22", "7"];
let numbers: Vec<_> = strings
let stringResult: Result<String, Error> =
.into_iter()
Ok(String::from("Bogdan"));
.filter_map(|s| s.parse::<i32>().ok())
.collect();
let userResult: Result<User, Error> =
stringResult.map(|name| {
User { name } Fail the entire operation with collect()
}); let strings = vec!["LGR", "22", "7"];
.and_then let numbers: Result<Vec<_>, _> = strings
.into_iter()
let vec = Some(vec![1, 2, 3]);
.map(|s| s.parse::<i32>())
let first_element = vec.and_then(
.collect();
|vec| vec.into_iter().next()
);
Collect all valid values and failures with partition()
let string_result: Result<&'static str, _> let strings = vec!["LGR", "22", "7"];
= Ok("5");
let number_result = let (numbers, errors): (Vec<_>, Vec<_>) =
string_result strings
.and_then(|s| s.parse::<u32>()); .into_iter()
.map(|s| s.parse::<i32>())
Multiple error types .partition(Result::is_ok);
Define custom error type
let numbers: Vec<_> = numbers
type Result<T> = std::result::Result<T, .into_iter()
CustomError>; .map(Result::unwrap)
.collect();
#[derive(Debug, Clone)]
let errors: Vec<_> = errors }
.into_iter()
.map(Result::unwrap_err) Default implementations with Derive
.collect();
// A tuple struct that can be printed
#[derive(Debug)]
struct Inches(i32);
Generics, Traits, and Lifetimes
Using generics Trait bounds
struct Point<T, U> { fn largest<T: PartialOrd + Copy>(list:
x: T, &[T]) -> T {
y: U, let mut largest = list[0];
}
for &item in list {
impl<T, U> Point<T, U> { if item > largest {
fn mixup<V, W>(self, other: Point<V, W>) largest = item;
-> Point<T, W> { }
Point { }
x: self.x,
y: other.y, largest
} }
}
} impl trait
fn make_adder_function(y: i32) -> impl
Defining traits Fn(i32) -> i32 {
trait Animal { let closure = move |x: i32| { x + y };
fn new(name: &'static str) -> Self; closure
fn noise(&self) -> &'static str { "" } }
}
Trait objects
struct Dog { name: &'static str } pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
impl Dog { }
fn fetch() { // ... }
}
Operator overloading
impl Animal for Dog { use std::ops::Add;
fn new(name: &'static str) -> Dog {
Dog { name: name } #[derive(Debug, Copy, Clone, PartialEq)]
} struct Point {
x: i32,
fn noise(&self) -> &'static str { y: i32,
"woof!" }
}
impl Add for Point { struct Point { x: i32, y: i32, }
type Output = Point;
impl Point {
fn add(self, other: Point) -> Point { // Associated function
Point { fn new(x: i32, y: i32) -> Point {
x: self.x + other.x, Point { x: x, y: y }
y: self.y + other.y, }
}
} // Method
} fn getX(&self) -> i32 { self.x }
}
Supertraits
use std::fmt; Function pointers
fn do_twice(f: fn(i32) -> i32, arg: i32) ->
trait Log: fmt::Display { i32 {
fn log(&self) { f(arg) + f(arg)
let output = self.to_string(); }
println!("Logging: {}", output);
} Creating closures
}
let add_one = |num: u32| -> u32 {
num + 1
Lifetimes in function signatures };
fn longest<'a>(x: &'a str, y: &'a str) ->
&'a str { Returning closures
if x.len() > y.len() {
x fn add_one() -> impl Fn(i32) -> i32 {
} else { |x| x + 1
y }
}
} fn add_or_subtract(x: i32) -> Box<dyn
Fn(i32) -> i32> {
if x > 10 {
Lifetimes in struct definitions Box::new(move |y| y + x)
struct User<'a> { } else {
full_name: &'a str, Box::new(move |y| y - x)
} }
}
Static lifetimes
let s: &'static str = "Let’s Get Rusty!"; Closure traits
● FnOnce - consumes the variables it captures
from its enclosing scope.
Functions, Function Pointers & ● FnMut - mutably borrows values from its
Closures enclosing scope.
● Fn - immutably borrows values from its enclosing
Associated functions and methods scope.
Store closure in struct borrowing rules at runtime instead of compile time.
struct Cacher<T> let num = 5;
where let r1 = RefCell::new(5);
T: Fn(u32) -> u32, // Ref - immutable borrow
{ let r2 = r1.borrow();
calculation: T, // RefMut - mutable borrow
value: Option<u32>, let r3 = r1.borrow_mut();
} // RefMut - second mutable borrow
let r4 = r1.borrow_mut();
Multiple owners of mutable data
let x = Rc::new(RefCell::new(5));
Function that accepts closure or function pointer
fn do_twice<T>(f: T, x: i32) -> i32
Packages, Crates, and Modules
where T: Fn(i32) -> i32 Definitions
{ ● Packages - A Cargo feature that lets you build,
f(x) + f(x) test, and share crates.
} ● Crates - A tree of modules that produces a
library or executable.
● Modules and use - Let you control the
Pointers organization, scope, and privacy of paths.
References ● Paths - A way of naming an item, such as a
let mut num = 5; struct, function, or module.
let r1 = # // immutable reference
let r2 = &mut num; // mutable reference Creating a new package with a binary crate
$ cargo new my-project
Raw pointers
let mut num = 5; Creating a new package with a library crate
// immutable raw pointer $ cargo new my-project --lib
let r1 = &num as *const i32;
// mutable raw pointer Defining and using modules
let r2 = &mut num as *mut i32;
fn some_function() {}
Smart pointers mod outer_module { // private module
Box<T> - for allocating values on the heap pub mod inner_module { // public module
let b = Box::new(5); pub fn inner_public_function() {
super::super::some_function();
}
Rc<T> - multiple ownership with reference counting
let a = Rc::new(5); fn inner_private_function() {}
let b = Rc::clone(&a); }
}
Ref<T>, RefMut<T>, and RefCell<T> - enforce
fn main() {
// absolute path
crate::outer_module::
inner_module::inner_public_function();
// relative path path
outer_module::
inner_module::inner_public_function();
// bringing path into scope
use outer_module::inner_module;
inner_module::inner_public_function();
}
Renaming with as keyword
use std::fmt::Result;
use std::io::Result as IoResult;
Re-exporting with pub use
mod outer_module {
pub mod inner_module {
pub fn inner_public_function() {}
}
}
pub use crate::outer_module::inner_module;
Defining modules in separate files
// src/lib.rs
mod my_module;
pub fn some_function() {
my_module::my_function();
}
// src/my_module.rs
pub fn my_function() {}