Let's Get Rusty Learning Guide 🦀
This is a space designed for aspiring Rust developers.
The curriculum is composed of existing YouTube videos by Let's Get Rusty, organized to guide you step-by-step through your Rust learning journey.
And to make your learning more interactive, there are exercises covering most of the topics. So, whether you're just starting out or looking to sharpen your skills in Rust, this is the perfect place for you.
To discuss and/or collaborate with other Rust developers, join our Discord community!
Features
-
Tutorial videos by Let's Get Rusty
-
Additional resources
-
Exercises
-
Exercise solutions
-
More user customizations coming soon!
Installation
git clone git@github.com:letsgetrusty/rust-learning-guide.git
cargo install mdbook
cd rust-learning-guide && mdbook serve --open
Credits
-
Bogdan :)
-
OP open-source community contributors!
Licenses
- MIT License
Setup
Note: To install Rust, head over to https://rustup.rs.
Additional Resources
Setup
Note: To install Rust, head over to https://rustup.rs.
Additional Resources
Hello World
Comments
Exercises
(No exercises yet. Feel free to contribute here!)
Variables
Additional Resources
Exercises
Declaration
// Fix the variable definition of 'x' fn main() { x = 5; println!("x has the value {}", x); }
Solution
fn main() { let x = 5; println!("x has the value {}", x); }
Declaration with reassignment
// Fix the variable definition of 'x' fn main() { let x = 3; println!("Number {}", x); x = 5; // don't change this line println!("Number {}", x); }
Solution
fn main() { let mut x = 3; println!("Number {}", x); x = 5; // don't change this line println!("Number {}", x); }
Declaration with shadowing
// Fix this code with shadowing fn main() { let x = "three"; // don't change this line println!("Spell a Number : {}", x); x = 3; // don't rename this variable println!("Number plus two is : {}", x + 2); }
Solution
fn main() { let x = "three"; // don't change this line println!("Spell a Number : {}", x); let x = 3; // don't rename this variable println!("Number plus two is : {}", x + 2); }
Data Types
Exercises
Boolean
// Something's missing. Fix the code so that it compiles. fn main() { let is_morning = true; if is_morning { println!("Good morning!"); } let // Finish the rest of this line if is_evening { println!("Good evening!"); } }
Solution
fn main() { let is_morning = true; if is_morning { println!("Good morning!"); } let is_evening = true; // can also assign false if is_evening { println!("Good evening!"); } }
Unsigned int
// Do you really have that few friends? // Assign the correct value to the variable. fn main() { let number_of_friends: u32; // Don't change this line! number_of_friends = -1; println!("I have {} friends!", number_of_friends); }
Solution
fn main() { let number_of_friends: u32; number_of_friends = 10; // any integer from 0 to 4294967295 is valid println!("I have {} friends!", number_of_friends); }
Signed int
// Make this program compile by replacing the variable type. fn main() { let number_of_stars: i32; // The Milky Way has more stars than can fit in a 32-bit integer type! number_of_stars = 400_000_000_000; println!("There are about {} stars in the Milky Way galaxy!", number_of_stars); }
Solution
fn main() { let number_of_stars: i64; number_of_stars = 400_000_000_000; println!("There are about {} stars in the Milky Way galaxy!", number_of_stars); }
Floating point numbers
// Assign the correct data types to the variables. fn main() { let pi2: f32; pi2 = 3.14; println!("Pi is {}, correct to 2 decimal places.", pi2); // What if we want to be more precise with our floating-point numbers? let pi15: /* Give this variable a data type! */; pi15 = 3.141592653589793; println!("Pi is {}, correct to 15 decimal places.", pi15); }
Solution
fn main() { let pi2: f32; pi2 = 3.14; println!("Pi is {}, correct to 2 decimal places.", pi2); let pi15: f64; pi15 = 3.141592653589793; println!("Pi is {}, correct to 15 decimal places.", pi15); }
char
// Fill in the rest of the line that has code missing! fn main() { let my_first_initial = 'C'; if my_first_initial.is_alphabetic() { println!("Alphabetical!"); } else if my_first_initial.is_numeric() { println!("Numerical!"); } else { println!("Neither alphabetic nor numeric!"); } let // Finish this line like the example! What's your favorite character? // Try a letter, try a number, try a special character, try a character // from a different language than your own, try an emoji! if your_character.is_alphabetic() { println!("Alphabetical!"); } else if your_character.is_numeric() { println!("Numerical!"); } else { println!("Neither alphabetic nor numeric!"); } }
Solution
fn main() { let my_first_initial = 'C'; if my_first_initial.is_alphabetic() { println!("Alphabetical!"); } else if my_first_initial.is_numeric() { println!("Numerical!"); } else { println!("Neither alphabetic nor numeric!"); } let your_character = '😃'; if your_character.is_alphabetic() { println!("Alphabetical!"); } else if your_character.is_numeric() { println!("Numerical!"); } else { println!("Neither alphabetic nor numeric!"); } }
String types
// Make this program compile without changing the variable type! fn main() { let answer: String = /* Your favorite color here */; println!("My current favorite color is {}", answer); }
Solution
fn main() { let answer: String = "black".to_string(); println!("My current favorite color is {}", answer); }
Arrays
// Create an array with at least 10 elements in it. fn main() { let a = /* Your array here */; if a.len() >= 10 { println!("Wow, that's a big array!"); } }
Solution
fn main() { let a = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; if a.len() >= 10 { println!("Wow, that's a big array!"); } }
Tuples
// Destructure the `cat` tuple so that the println will work. fn main() { let cat = ("Furry McFurson", 3.5); let /* Your pattern here */ = cat; println!("{} is {} years old.", name, age); }
Solution
fn main() { let cat = ("Furry McFurson", 3.5); let (name, age) = cat; println!("{} is {} years old.", name, age); }
Type aliasing
// Add a type alias for our dogs so we can store their names and ages. fn main() { type /* Finish this line */ let dog1: Dog = (String::from("Albert"), 3); println!("My dog {} is {} years old.", dog1.0, dog1.1); let dog2: Dog = (String::from("Barker"), 5); println!("My other dog {} is {} years old.", dog2.0, dog2.1); }
Solution
fn main() { type Dog = (String, u8); let dog1: Dog = (String::from("Albert"), 3); println!("My dog {} is {} years old.", dog1.0, dog1.1); let dog2: Dog = (String::from("Barker"), 5); println!("My other dog {} is {} years old.", dog2.0, dog2.1); }
Constants & Statics
Additional Resources
Exercises
Constants
// Fix the code so it compiles! const NUMBER = 3; fn main() { println!("Number {}", NUMBER); }
Solution
const NUMBER: i32 = 3; fn main() { println!("Number {}", NUMBER); }
Statics
// Fix the code so it compiles! static LANGUAGE = "Go"; fn main() { // These two initializations perform a string copy from same memory location let lang1 = LANGUAGE; let mut lang2 = LANGUAGE; lang2 = "Rust"; println!("I like {} more than {}!", lang2, lang1); }
Solution
static LANGUAGE: &str = "Go"; fn main() { // These two initializations perform a string copy from same memory location let lang1 = LANGUAGE; let mut lang2 = LANGUAGE; lang2 = "Rust"; println!("I like {} more than {}!", lang2, lang1); }
Functions
Exercises
Definition
// Something's missing from the function definition. Fix it so it compiles! fn main() { call_me(9395550113); } fn call_me(num:) { println!("Ring! Call number {}", num); }
Solution
fn main() { call_me(9395550113); } fn call_me(num: i64) { println!("Ring! Call number {}", num); }
Return types
// This store is having a sale where if the price is an even number, you get // 10 Rustbucks off, but if it's an odd number, it's 3 Rustbucks off. // But it won't compile--fix it // (Don't worry about the function bodies themselves, we're only interested // in the signatures for now) fn main() { let original_price = 51; println!("Your sale price is {}", sale_price(original_price)); } fn sale_price(price: i32) -> { if is_even(price) { price - 10 } else { price - 3 } } fn is_even(num: i32) -> bool { num % 2 == 0 }
Solution
fn main() { let original_price = 51; println!("Your sale price is {}", sale_price(original_price)); } fn sale_price(price: i32) -> i32 { if is_even(price) { price - 10 } else { price - 3 } } fn is_even(num: i32) -> bool { num % 2 == 0 }
Return keyword
// This function describes a morning in the life of a Rust programmer. // She has a few morning rituals, but she skips all that if she wakes up late! // Fix the morning_routine function to return early when the Rust programmer wakes up late. fn main() { let i_woke_up_late = true; morning_routine(i_woke_up_late); } fn morning_routine(i_am_late: bool) { println!("This morning, I..."); if i_am_late { go_to_work(); } exercise(); eat_breakfast(); make_coffee(); go_to_work(); } fn exercise() { println!("I went to the gym."); } fn eat_breakfast() { println!("I had a healthy breakfast!"); } fn make_coffee() { println!("I made myself coffee. Now that I'm ready..."); } fn go_to_work() { println!("I went straight to work!"); }
Solution
fn main() { let i_woke_up_late = true; morning_routine(i_woke_up_late); } fn morning_routine(i_am_late: bool) { println!("This morning, I..."); if i_am_late { go_to_work(); return; } exercise(); eat_breakfast(); make_coffee(); go_to_work(); } fn exercise() { println!("I went to the gym."); } fn eat_breakfast() { println!("I had a healthy breakfast!"); } fn make_coffee() { println!("I made myself coffee. Now that I'm ready..."); } fn go_to_work() { println!("I went straight to work!"); }
Control Flow
Exercises
If/else
pub fn bigger(a: i32, b: i32) -> i32 { // Complete this function to return the bigger number! // Do not use: // - another function call // - additional variables } // Don't mind this for now :) #[cfg(test)] mod tests { use super::*; #[test] fn ten_is_bigger_than_eight() { assert_eq!(10, bigger(10, 8)); } #[test] fn fortytwo_is_bigger_than_thirtytwo() { assert_eq!(42, bigger(32, 42)); } }
Solution
#![allow(unused)] fn main() { pub fn bigger(a: i32, b: i32) -> i32 { if a > b { a } else { b } } // Don't mind this for now :) #[cfg(test)] mod tests { use super::*; #[test] fn ten_is_bigger_than_eight() { assert_eq!(10, bigger(10, 8)); } #[test] fn fortytwo_is_bigger_than_thirtytwo() { assert_eq!(42, bigger(32, 42)); } } }
Loop
use num::integer::sqrt; // There's a prime number hiding in our array of integers! // The function below tries to find the prime number by checking each element, // and finding its divisor. If none is found, then it's a prime number and // its search ends! // But it seems that its search never does end, when there's clearly a // prime number there. Fix the function so that it returns the prime number. fn main() { let numbers = [36, 25, 49, 3, 64, 16, 9]; let prime = get_prime(numbers); } fn get_prime(arr: [i32; 7]) -> i32 { // Loop through every element in the array let mut i = 0; 'outer: loop { // Find a divisor. let mut n = 2; 'inner: loop { // If a number can be evenly divided by another number except 1 and itself, // then it's not a prime. // Break out here to move on to the next element. if arr[i] % n == 0 { if arr[i] == 2 { break 'outer; } i += 1; break; } // If no divisors are found, then we've found a prime! // Break out of the loop here. if n >= sqrt(arr[i]) { break; } // Otherwise, move to the next element. n += 1; } } println!("The first prime number in the array is {}.", arr[i]); arr[i] }
Solution
use num::integer::sqrt; fn main() { let numbers = [36, 25, 49, 3, 64, 16, 9]; let prime = get_prime(numbers); } fn get_prime(arr: [i32; 7]) -> i32 { let mut i = 0; 'outer: loop { let mut n = 2; 'inner: loop { if arr[i] % n == 0 { if arr[i] == 2 { break 'outer; } i += 1; break; } if n >= sqrt(arr[i]) { break 'outer; } n += 1; } } println!("The first prime number in the array is {}.", arr[i]); arr[i] }
While
// Below is the classic FizzBuzz program. It prints every number from 1 to 100, // except for multiples of 3 it prints "fizz" instead of the number, and for // multiples of 5 it prints "buzz" instead of the number. If the number is // both a multiple of 3 and 5, it prints "fizzbuzz". // Fix the compile time error so that the program runs successfully. fn main() { let mut n = 1; while { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } n += 1; } }
Solution
fn main() { let mut n = 1; while n <= 100 { if n % 15 == 0 { println!("fizzbuzz"); } else if n % 3 == 0 { println!("fizz"); } else if n % 5 == 0 { println!("buzz"); } else { println!("{}", n); } n += 1; } }
For
// Here's a bucket list of cities I'd like to visit one day, and I'd like to // share it with the world. Fix the loop so it announces the cities I'd like to visit. fn main() { let cities = ["Perth", "Qingdao", "Rome"]; for cities { println!("I'd like to visit {} someday!", city); } }
Solution
fn main() { let cities = ["Perth", "Qingdao", "Rome"]; for city in cities { println!("I'd like to visit {} someday!", city); } }
Ownership
Additional Resources
Exercises
Moving on assignment
// Something's missing. Fix the code so that it compiles. fn main() { let s1 = String::from("Rust"); let mut s2 = s1; s2.push_str(" is an awesome language"); println!("String:\"{s1}\" is a substring of \"{s2}\""); }
Solution
fn main() { let s1 = String::from("Rust"); let mut s2 = s1.clone(); s2.push_str(" is an awesome language"); println!("String:\"{s1}\" is a substring of \"{s2}\""); }
Moving on assignment 2
// Fix the code so that it compiles. Modify only one statement. fn main() { let mut my_str = String::from("Example"); let mut temp; while my_str.len() > 0 { temp = my_str; println!("Length of temporary string is: {}", temp.len()); my_str.pop(); } }
Solution
Description
The ownership of a value is assigned upon the assignment of the value itself. We need to be aware of the ownership of a value every time an assignment of a value occurs.
fn main() { let mut my_str = String::from("Example"); let mut temp; while my_str.len() > 0 { temp = my_str.clone(); println!("Length of temporary string is: {}", temp.len()); my_str.pop(); } }
Moving into function
// Fix the code so that it compiles. fn main() { let my_string = String::from("I love rust bootcamp 💕"); let occurence_count = count_occurences(my_string, 'o'); println!("The number of times 'o' apprears in \"{my_string}\" = {occurence_count}"); } // this function counts the number of times a letter appears in a text fn count_occurences(text: String, letter: char) -> u32 { let mut res = 0; for ch in text.chars() { if ch == letter { res += 1; } } res }
Solution
fn main() { let my_string = String::from("I love rust bootcamp 💕"); let occurence_count = count_occurences(&my_string, 'o'); println!("The number of times 'o' apprears in \"{my_string}\" = {occurence_count}"); } // this function counts the number of times a letter appears in a text fn count_occurences(text: &String, letter: char) -> u32 { let mut res = 0; for ch in text.chars() { if ch == letter { res += 1; } } res }
Moving out of function
// Make the following code compile by modifying only one statement. fn main() { let mut str1 = get_new_string(); println!("Printing through str1: {}", str1); let mut str2 = str1; println!("Printing through str2: {}", str2); str1 = str2; println!("Again printing through str1: {}", str1); str2 = str1; println!("Again printing through str2: {}", str2); println!("Printing thourgh both: {}, {}", str1, str2); } fn get_new_string() -> String { let new_string = String::from("I will master rust 🦀 🦀"); new_string } // string ownership is transferred to the calling function
Solution
fn main() { let mut str1 = get_new_string(); println!("Printing through str1: {}", str1); let mut str2 = str1; println!("Printing through str2: {}", str2); str1 = str2; println!("Again printing through str1: {}", str1); str2 = str1.clone(); println!("Again printing through str2: {}", str2); println!("Printing thourgh both: {}, {}", str1, str2); } fn get_new_string() -> String { let new_string = String::from("I will master rust 🦀 🦀"); new_string } // string ownership is transferred to the calling function
Ownership
Additional Resources
Exercises
Moving on assignment
// Something's missing. Fix the code so that it compiles. fn main() { let s1 = String::from("Rust"); let mut s2 = s1; s2.push_str(" is an awesome language"); println!("String:\"{s1}\" is a substring of \"{s2}\""); }
Solution
fn main() { let s1 = String::from("Rust"); let mut s2 = s1.clone(); s2.push_str(" is an awesome language"); println!("String:\"{s1}\" is a substring of \"{s2}\""); }
Moving on assignment 2
// Fix the code so that it compiles. Modify only one statement. fn main() { let mut my_str = String::from("Example"); let mut temp; while my_str.len() > 0 { temp = my_str; println!("Length of temporary string is: {}", temp.len()); my_str.pop(); } }
Solution
Description
The ownership of a value is assigned upon the assignment of the value itself. We need to be aware of the ownership of a value every time an assignment of a value occurs.
fn main() { let mut my_str = String::from("Example"); let mut temp; while my_str.len() > 0 { temp = my_str.clone(); println!("Length of temporary string is: {}", temp.len()); my_str.pop(); } }
Moving into function
// Fix the code so that it compiles. fn main() { let my_string = String::from("I love rust bootcamp 💕"); let occurence_count = count_occurences(my_string, 'o'); println!("The number of times 'o' apprears in \"{my_string}\" = {occurence_count}"); } // this function counts the number of times a letter appears in a text fn count_occurences(text: String, letter: char) -> u32 { let mut res = 0; for ch in text.chars() { if ch == letter { res += 1; } } res }
Solution
fn main() { let my_string = String::from("I love rust bootcamp 💕"); let occurence_count = count_occurences(&my_string, 'o'); println!("The number of times 'o' apprears in \"{my_string}\" = {occurence_count}"); } // this function counts the number of times a letter appears in a text fn count_occurences(text: &String, letter: char) -> u32 { let mut res = 0; for ch in text.chars() { if ch == letter { res += 1; } } res }
Moving out of function
// Make the following code compile by modifying only one statement. fn main() { let mut str1 = get_new_string(); println!("Printing through str1: {}", str1); let mut str2 = str1; println!("Printing through str2: {}", str2); str1 = str2; println!("Again printing through str1: {}", str1); str2 = str1; println!("Again printing through str2: {}", str2); println!("Printing thourgh both: {}, {}", str1, str2); } fn get_new_string() -> String { let new_string = String::from("I will master rust 🦀 🦀"); new_string } // string ownership is transferred to the calling function
Solution
fn main() { let mut str1 = get_new_string(); println!("Printing through str1: {}", str1); let mut str2 = str1; println!("Printing through str2: {}", str2); str1 = str2; println!("Again printing through str1: {}", str1); str2 = str1.clone(); println!("Again printing through str2: {}", str2); println!("Printing thourgh both: {}, {}", str1, str2); } fn get_new_string() -> String { let new_string = String::from("I will master rust 🦀 🦀"); new_string } // string ownership is transferred to the calling function
Borrowing
Additional Resources
Exercises
Immutable references
// Fix the code so that it compiles. fn main() { let mut str1 = String::from("modifiable"); let str2 = String::from("fixed string"); let mut str_ptr: &String; str_ptr = str1; println!("ptr currently points to {str_ptr}"); str_ptr = str2; println!("ptr currently points to {str_ptr}"); str1.push_str(" string"); str_ptr = str1; println!("ptr currently points to {str_ptr}"); }
Solution
fn main() { let mut str1 = String::from("modifiable"); let str2 = String::from("fixed string"); let mut str_ptr: &String; str_ptr = &str1; println!("ptr currently points to {str_ptr}"); str_ptr = &str2; println!("ptr currently points to {str_ptr}"); str1.push_str(" string"); str_ptr = &str1; println!("ptr currently points to {str_ptr}"); }
Mutable references 1
// Fix the code so that it compiles. fn main() { let mut s = String::from("Hello, "); let s_ref = &s; change_string(s_ref); println!("{s_ref}"); } fn change_string(s: &String) { s.push_str(" world!"); }
Solution
fn main() { let mut s = String::from("Hello, "); let s_ref = &mut s; change_string(s_ref); println!("{s_ref}"); } fn change_string(s: &mut String) { s.push_str(" world!"); }
Mutable references 2
// Fix the code so that it compiles. fn main() { let str1 = String::from("Rust"); let str2 = String::from("Golang"); let ref1 = &mut str1; let mut ref2 = &mut str2; println!("First string: {ref1}"); println!("Second string: {ref2}"); ref1.push('🦀'); ref2.push('🦫'); println!("Modified first string: {ref1}"); println!("Modified second string: {ref2}"); // only one mutable reference allowed at a time, ref1 is no longer valid ref2 = &mut str1; ref2.pop(); println!("Original first string: {ref2}"); }
Solution
fn main() { let mut str1 = String::from("Rust"); let mut str2 = String::from("Golang"); let ref1 = &mut str1; let mut ref2 = &mut str2; println!("First string: {ref1}"); println!("Second string: {ref2}"); ref1.push('🦀'); ref2.push('🦫'); println!("Modified first string: {ref1}"); println!("Modified second string: {ref2}"); // only one mutable reference allowed at a time, ref1 is no longer valid ref2 = &mut str1; ref2.pop(); println!("Original first string: {ref2}"); }
Passing by reference
// Complete the function signature to make the code compile. fn main() { let mut s1 = String::from("this is "); let s2 = String::from("an example sentence"); concat(&mut s1, &s2); println!("{s1}") } fn concat(s1, s2) { for ch in s2.chars() { s1.push(ch); } }
Solution
fn main() { let mut s1 = String::from("this is "); let s2 = String::from("an example sentence"); concat(&mut s1, &s2); println!("{s1}") } fn concat(s1: &mut String, s2: &str) { for ch in s2.chars() { s1.push(ch); } }
Slices
Exercises
String slice
// Complete the function definition to make the code compile. // The output of the code should be logically correct. fn main() { let text = String::from("Today is a very warm and sunny day."); let words = ["very", "arm", "say", "sun", "dew"]; let mut pos; println!("Text: {text}"); for word in words { pos = find_substr_pos(&text, word); if pos == text.len() { println!("{word} is not present in text"); } else { println!("{word} present at index {pos}"); } } } // this function tries to search for substr in text from left to right // if it finds substr, it returns the index where it starts // otherwise it returns length of text (which is an invalid index) fn find_substr_pos(text, substr) -> usize { // both parameters should have same data type if text.len() < substr.len() { return text.len(); } let len = substr.len(); for start in 0..text.len() - len + 1 { if substr == &text[] { // what will be the correct range? return start; } } text.len() }
Solution
fn main() { let text = String::from("Today is a very warm and sunny day."); let words = ["very", "arm", "say", "sun", "dew"]; let mut pos; println!("Text: {text}"); for word in words { pos = find_substr_pos(&text, word); if pos == text.len() { println!("{word} is not present in text"); } else { println!("{word} present at index {pos}"); } } } fn find_substr_pos(text: &str, substr: &str) -> usize { if text.len() < substr.len() { return text.len(); } let len = substr.len(); for start in 0..text.len() - len + 1 { if substr == &text[start..start+len] { return start; } } text.len() }
Array slice
// Fix the program so that it compiles successfully and produces the desired output. fn main() { let nums = [1, 1, 2, 3, 5, 8, 13]; let res = find_subarray(&nums[..], 16); if res.0 == nums.len() { println!("No subarray found"); } else { println!("Subarray found: {:?}", &nums[res.0..res.0 + res.1]); } } // this function searches an array to find a subarray with the given sum // it returns the index where the subarray starts along with the length of the subarray // if the array does not include any subarray with the sum, it returns a tuple with length or array fn find_subarray(nums: &[i32], sum: i32) -> (usize, usize) { for len in (1..nums.len() + 1).rev() { for start in 0..nums.len() - len + 1 { if array_sum(&nums[]) == sum { return (start, len); } } } (nums.len(), nums.len()) } fn array_sum(nums) -> i32 { let mut res = 0; for num in nums { res += num; } res }
Solution
fn main() { let nums = [1, 1, 2, 3, 5, 8, 13]; let res = find_subarray(&nums[..], 16); if res.0 == nums.len() { println!("No subarray found"); } else { println!("Subarray found: {:?}", &nums[res.0..res.0 + res.1]); } } fn find_subarray(nums: &[i32], sum: i32) -> (usize, usize) { for len in (1..nums.len() + 1).rev() { for start in 0..nums.len() - len + 1 { if array_sum(&nums[start..start+len]) == sum { return (start, len); } } } (nums.len(), nums.len()) } fn array_sum(nums: &[i32]) -> i32 { let mut res = 0; for num in nums { res += num; } res }
Strings
Exercises
(No exercises yet. Feel free to contribute here!)
Structs
Exercises
Struct definition
// Complete the code by addressing the TODO. struct User { // TODO: Something goes here } fn main() { let user = User { name: String::from("Tom Riddle"), age: 17u8, }; println!("User's name: {}", user.name); println!("User's age: {}", user.age); }
Solution
struct User { name: String, age: u8, } fn main() { let user = User { name: String::from("Tom Riddle"), age: 17u8, }; println!("User's name: {}", user.name); println!("User's age: {}", user.age); }
Mutating structs
// Make the following code compile. struct ShopItem { name: String, quantity: u32, in_stock: bool, } fn main() { let item = ShopItem { name: String::from("Socks"), quantity: 200, in_stock: true, }; // 50 pairs of socks were sold item.quantity -= 50; if item.quantity == 0 { item.in_stock = false; } println!("{} is in stock: {}", item.name, item.in_stock); }
Solution
struct ShopItem { name: String, quantity: u32, in_stock: bool, } fn main() { let mut item = ShopItem { name: String::from("Socks"), quantity: 200, in_stock: true, }; // 50 pairs of socks were sold item.quantity -= 50; if item.quantity == 0 { item.in_stock = false; } println!("{} is in stock: {}", item.name, item.in_stock); }
Structs and Functions
// Complete the function signatures and make the code compile. struct ShopItem { name: String, quantity: u32, } fn main() { let item = create_item("Socks", 200); let in_stock = is_in_stock(&item); println!("{} is in stock: {in_stock}", item.name); } fn create_item(name: &str, quantity: u32) -> { ShopItem { name: name.to_string(), quantity, // notice how struct initializations can be shortened when variable and field have same name } } fn is_in_stock(item) -> bool { item.quantity > 0 }
Solution
struct ShopItem { name: String, quantity: u32, } fn main() { let item = create_item("Socks", 200); let in_stock = is_in_stock(&item); println!("{} is in stock: {in_stock}", item.name); } fn create_item(name: &str, quantity: u32) -> ShopItem { ShopItem { name: name.to_string(), quantity, } } fn is_in_stock(item: &ShopItem) -> bool { item.quantity > 0 }
Structs
Exercises
Struct definition
// Complete the code by addressing the TODO. struct User { // TODO: Something goes here } fn main() { let user = User { name: String::from("Tom Riddle"), age: 17u8, }; println!("User's name: {}", user.name); println!("User's age: {}", user.age); }
Solution
struct User { name: String, age: u8, } fn main() { let user = User { name: String::from("Tom Riddle"), age: 17u8, }; println!("User's name: {}", user.name); println!("User's age: {}", user.age); }
Mutating structs
// Make the following code compile. struct ShopItem { name: String, quantity: u32, in_stock: bool, } fn main() { let item = ShopItem { name: String::from("Socks"), quantity: 200, in_stock: true, }; // 50 pairs of socks were sold item.quantity -= 50; if item.quantity == 0 { item.in_stock = false; } println!("{} is in stock: {}", item.name, item.in_stock); }
Solution
struct ShopItem { name: String, quantity: u32, in_stock: bool, } fn main() { let mut item = ShopItem { name: String::from("Socks"), quantity: 200, in_stock: true, }; // 50 pairs of socks were sold item.quantity -= 50; if item.quantity == 0 { item.in_stock = false; } println!("{} is in stock: {}", item.name, item.in_stock); }
Structs and Functions
// Complete the function signatures and make the code compile. struct ShopItem { name: String, quantity: u32, } fn main() { let item = create_item("Socks", 200); let in_stock = is_in_stock(&item); println!("{} is in stock: {in_stock}", item.name); } fn create_item(name: &str, quantity: u32) -> { ShopItem { name: name.to_string(), quantity, // notice how struct initializations can be shortened when variable and field have same name } } fn is_in_stock(item) -> bool { item.quantity > 0 }
Solution
struct ShopItem { name: String, quantity: u32, } fn main() { let item = create_item("Socks", 200); let in_stock = is_in_stock(&item); println!("{} is in stock: {in_stock}", item.name); } fn create_item(name: &str, quantity: u32) -> ShopItem { ShopItem { name: name.to_string(), quantity, } } fn is_in_stock(item: &ShopItem) -> bool { item.quantity > 0 }
Implementation Blocks
Exercises
Methods
// Complete the method signatures by providing appropriate arguments. struct Student { first_name: String, last_name: String, roll_no: u16, } impl Student { fn get_name() -> String { format!("{} {}", self.first_name, self.last_name) } fn set_roll_no(, new_roll_no: u16) { self.roll_no = new_roll_no; } fn convert_to_string() -> String { // should take ownership format!( "Name: {} {}, Roll no: {}", self.first_name, self.last_name, self.roll_no ) } } fn main() { let mut student = Student { first_name: "Harry".to_string(), last_name: "Potter".to_string(), roll_no: 42, }; println!("Student is: {}", student.get_name()); student.set_roll_no(50); let student_details = student.convert_to_string(); println!("{student_details}"); }
Solution
struct Student { first_name: String, last_name: String, roll_no: u16, } impl Student { fn get_name(&self) -> String { format!("{} {}", self.first_name, self.last_name) } fn set_roll_no(&mut self, new_roll_no: u16) { self.roll_no = new_roll_no; } fn convert_to_string(self) -> String { format!( "Name: {} {}, Roll no: {}", self.first_name, self.last_name, self.roll_no ) } } fn main() { let mut student = Student { first_name: "Harry".to_string(), last_name: "Potter".to_string(), roll_no: 42, }; println!("Student is: {}", student.get_name()); student.set_roll_no(50); let student_details = student.convert_to_string(); println!("{student_details}"); }
Associated functions
// Fix the code so that it compiles. struct ShopItem { name: String, quantity: u32, } impl ShopItem { fn new(name: String, quantity: u32) -> ShopItem { ShopItem { name, quantity } } fn in_stock(&self) -> bool { self.quantity > 0 } } fn main() { let item = ShopItem.new("Pants".to_string(), 450); if item.in_stock() { println!("{} remaining: {}", item.name, item.quantity); } else { println!("{} not in stock", item.name); } }
Solution
struct ShopItem { name: String, quantity: u32, } impl ShopItem { fn new(name: String, quantity: u32) -> ShopItem { ShopItem { name, quantity } } fn in_stock(&self) -> bool { self.quantity > 0 } } fn main() { let item = ShopItem::new("Pants".to_string(), 450); if item.in_stock() { println!("{} remaining: {}", item.name, item.quantity); } else { println!("{} not in stock", item.name); } }
Tuple Structs
Exercises
Definition
Description
This is a helpful description. Read me to understand what to do!
// Complete the structure definition. struct Point impl Point { fn on_x_axis(&self) -> bool { self.1 == 0.0 } fn on_y_axis(&self) -> bool { self.0 == 0.0 } } fn main() { let point = Point(0.0, 0.0); if point.on_x_axis() && point.on_y_axis() { println!("Point is origin"); } }
Hint 1
This is a helpful hint! Read me to understand what to do!
Hint 2
This is a helpful hint! Read me to understand what to do!
Hint 3
This is a helpful hint! Read me to understand what to do!
Solution
struct Point(f32, f32); impl Point { fn on_x_axis(&self) -> bool { self.1 == 0.0 } fn on_y_axis(&self) -> bool { self.0 == 0.0 } } fn main() { let point = Point(0.0, 0.0); if point.on_x_axis() && point.on_y_axis() { println!("Point is origin"); } }
Enums
Exercises
Definition 1
// Complete the code by addressing the TODO. #[derive(Debug)] // this line makes the enum variants printable! enum Message { // TODO: define a few types of messages as used below } fn main() { println!("{:?}", Message::Quit); println!("{:?}", Message::Echo); println!("{:?}", Message::Move); println!("{:?}", Message::ChangeColor); }
Solution
#[derive(Debug)] enum Message { Quit, Echo, Move, ChangeColor, } fn main() { println!("{:?}", Message::Quit); println!("{:?}", Message::Echo); println!("{:?}", Message::Move); println!("{:?}", Message::ChangeColor); }
Definition 2
// Complete the code by addressing the TODO. #[derive(Debug)] // this line makes the enum variants printable! enum Message { // TODO: define the different variants used below } impl Message { fn call(&self) { println!("{:?}", self); } } fn main() { let messages = [ Message::Move { x: 10, y: 30 }, Message::Echo(String::from("hello world")), Message::ChangeColor(200, 255, 255), Message::Quit, ]; for message in &messages { message.call(); } }
Solution
#[derive(Debug)] enum Message { Move { x: i32, y: i32 }, Echo(String), ChangeColor(u8, u8, u8), Quit, } impl Message { fn call(&self) { println!("{:?}", self); } } fn main() { let messages = [ Message::Move { x: 10, y: 30 }, Message::Echo(String::from("hello world")), Message::ChangeColor(200, 255, 255), Message::Quit, ]; for message in &messages { message.call(); } }
Matching
Exercises
Exhaustive requirement
// Make the following code compile. // If you score 50 or less, you fail. fn main() { // marks scored out of 100 let marks = 75u8; match marks { 91..=100 => println!("You performed excellent!"), 71..=90 => println!("You performed good :)"), 51..=70 => println!("Your performance was average..."), 0..=30 => println!("You failed. Better luck next time."), 101..=u8::MAX => println!("Invalid marks!!!"), } }
Solution
fn main() { let side_count = 5; let message = match side_count { 0 | 1 | 2 => "invalid shape", 3 => "it's a triangle", 4 => "it's a quadrilateral", 5 => "it's a pentagon", 6 => "it's a hexagon", _ => "i don't know the name, lol", }; println!("{message}"); }
Enum matching 1
// Fix the code so that it compiles. // USD coin types // cent values: penny:1, nickel:5, dime: 10, quarter:25 enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Quarter => 25, } } fn main() { let piggy_bank = [Coin::Nickel, Coin::Penny, Coin::Dime, Coin::Penny]; let mut my_savings = 0; for coin in piggy_bank { my_savings += value_in_cents(coin); } println!("My savings: {my_savings} cents"); }
Solution
enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } } fn main() { let piggy_bank = [Coin::Nickel, Coin::Penny, Coin::Dime, Coin::Penny]; let mut my_savings = 0; for coin in piggy_bank { my_savings += value_in_cents(coin); } println!("My savings: {my_savings} cents"); }
Enum matching 2
// Fix the code so that it compiles. enum Operation { Add(u8, u8), Mul(u8, u8), Subtract { first: u8, second: u8 }, Divide { divident: u8, divisor: u8 }, } impl Operation { fn result(&self) -> u8 { match self { Self::Add(a, b) => a + b, // notice Self can be used instead of Operation Self::Subtract { first, second } => first - second, Self::Divide { divident, divisor } => divident / divisor, } } } fn main() { let user_operation = Operation::Subtract { first: 75, second: 20, }; println!("Result: {}", user_operation::result()); }
Solution
enum Operation { Add(u8, u8), Mul(u8, u8), Subtract { first: u8, second: u8 }, Divide { divident: u8, divisor: u8 }, } impl Operation { fn result(&self) -> u8 { match self { Self::Add(a, b) => a + b, Self::Mul(x, y) => x * y, Self::Subtract { first, second } => first - second, Self::Divide { divident, divisor } => divident / divisor, } } } fn main() { let user_operation = Operation::Subtract { first: 75, second: 20, }; println!("Result: {}", user_operation.result()); }
Option
Exercises
Matching option 1
// Fix the code so that it compiles. struct Point { x: i32, y: i32, } fn main() { let y: Option<Point> = Some(Point { x: 100, y: 200 }); match y { Some(p) => println!("Co-ordinates are {},{} ", p.x, p.y), _ => println!("no match"), } y; // Fix without deleting this line. }
Solution
struct Point { x: i32, y: i32, } fn main() { let y: Option<Point> = Some(Point { x: 100, y: 200 }); match &y { Some(p) => println!("Co-ordinates are {},{} ", p.x, p.y), _ => println!("no match"), } y; // Fix without deleting this line. }
Matching option 2
// Fix the code so that it compiles. fn last_element(nums: &[i32]) -> Option<i32> { if nums.len() > 0 { Some(nums[nums.len() - 1]) } else { None } } fn main() { let my_nums = [1, 1, 2, 3, 5, 8, 13]; match last_element(&my_nums) { Some => println!("Last element: {ele}"), None => println!("Empty array"), } }
Solution
fn last_element(nums: &[i32]) -> Option<i32> { if nums.len() > 0 { Some(nums[nums.len() - 1]) } else { None } } fn main() { let my_nums = [1, 1, 2, 3, 5, 8, 13]; match last_element(&my_nums) { Some(ele) => println!("Last element: {ele}"), None => println!("Empty array"), } }
If let
// Fix the code so that it compiles. struct User { id: u32, name: String, } fn get_user_name(id: u32) -> Option<String> { let database = [ User {id: 1, name: String::from("Alice")}, User {id: 2, name: String::from("Bob")}, User {id: 3, name: String::from("Cindy")} ]; for user in database { if user.id == id { return Some(user.name) } } None } fn main() { let user_id = 3; if Some(name) == get_user_name(user_id) { println!("User's name: {name}"); } }
Solution
struct User { id: u32, name: String, } fn get_user_name(id: u32) -> Option<String> { let database = [ User {id: 1, name: String::from("Alice")}, User {id: 2, name: String::from("Bob")}, User {id: 3, name: String::from("Cindy")} ]; for user in database { if user.id == id { return Some(user.name) } } None } fn main() { let user_id = 3; if let Some(name) = get_user_name(user_id) { println!("User's name: {name}"); } }
Result
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Exercises
Mathing result
// Complete the match statement to check whether operation succeeded or not. enum Operation { Add(i32, i32), Mul(i32, i32), Sub { first: i32, second: i32 }, Div { divident: i32, divisor: i32 }, } impl Operation { fn execute(self) -> Result<i32, String> { match self { Self::Add(a, b) => Ok(a + b), Self::Mul(a, b) => Ok(a * b), Self::Sub { first, second } => Ok(first - second), Self::Div { divident, divisor } => { if divisor == 0 { Err(String::from("Can not divide by zero")) } else { Ok(divident / divisor) } } } } } fn main() { let user_input = Operation::Div { divident: 20, divisor: 0, }; match user_input.execute() { => println!("Result: {res}"), => println!("Error: {e}"), } }
Solution
enum Operation { Add(i32, i32), Mul(i32, i32), Sub { first: i32, second: i32 }, Div { divident: i32, divisor: i32 }, } impl Operation { fn execute(self) -> Result<i32, String> { match self { Self::Add(a, b) => Ok(a + b), Self::Mul(a, b) => Ok(a * b), Self::Sub { first, second } => Ok(first - second), Self::Div { divident, divisor } => { if divisor == 0 { Err(String::from("Can not divide by zero")) } else { Ok(divident / divisor) } } } } } fn main() { let user_input = Operation::Div { divident: 20, divisor: 0, }; match user_input.execute() { Ok(res) => println!("Result: {res}"), Err(e) => println!("Error: {e}"), } }
Returning result
// Complete the function signature. fn greet(name: &str) -> { if name.len() > 0 { println!("Hello {name}!"); Ok(()) } else { Err("Empty name provided".to_string()) } } fn main() { let name = "Tom"; if let Err(e) = greet(name) { println!("Error: {e}"); } }
Solution
fn greet(name: &str) -> Result<(), String> { if name.len() > 0 { println!("Hello {name}!"); Ok(()) } else { Err("Empty name provided".to_string()) } } fn main() { let name = "Tom"; if let Err(e) = greet(name) { println!("Error: {e}"); } }
Vectors
Additional Resources
Exercises
Pushing
// Complete the function to make the program execute successfully. fn append(nums, num) { } fn main() { let mut nums = vec![1, 2, 5, 6]; append(&mut nums, 8); append(&mut nums, 3); assert_eq!(nums.len(), 6); }
Solution
fn append(nums: &mut Vec<i32>, num: i32) { nums.push(num); } fn main() { let mut nums = vec![1, 2, 5, 6]; append(&mut nums, 8); append(&mut nums, 3); assert_eq!(nums.len(), 6); }
Removing
// Complete the function to make the program execute successfully. fn remove_if_odd(nums, index) { if index > nums.len() { println!("Index out of bounds"); return; } } fn main() { let mut nums = vec![1, 2, 6, 9]; let nums_ref = &mut nums; remove_if_odd(nums_ref, 0); remove_if_odd(nums_ref, 1); remove_if_odd(nums_ref, nums_ref.len() - 1); assert_eq!(nums.len(), 2); }
Solution
fn remove_if_odd(nums: &mut Vec<i32>, index: usize) { if index > nums.len() { println!("Index out of bounds"); return; } if nums[index] % 2 == 1 { nums.remove(index); } } fn main() { let mut nums = vec![1, 2, 6, 9]; let nums_ref = &mut nums; remove_if_odd(nums_ref, 0); remove_if_odd(nums_ref, 1); remove_if_odd(nums_ref, nums_ref.len() - 1); assert_eq!(nums.len(), 2); }
Fetching
// Fix the code so that it compiles. fn main() { let names = vec!["Alice", "Bob", "Cindy"]; let index = 2; if names.get(index) { println!("{name} is present at index {index}"); } else { println!("invalid index {index}"); } }
Solution
fn main() { let names = vec!["Alice", "Bob", "Cindy"]; let index = 2; if let Some(name) = names.get(index) { println!("{name} is present at index {index}"); } else { println!("invalid index {index}"); } }
Iterating
// Fix the code so that it compiles. struct Student { name: String, marks: u8, } impl Student { fn new(name: &str, marks: u8) -> Self { Self { name: name.to_string(), marks, } } } fn main() { let students = vec![ Student::new("Harry", 75), Student::new("Hermoine", 99), Student::new("Ron", 60), ]; let mut grades = Vec::new(); for student in students { if student.marks > 80 { grades.push('A'); } else if student.marks > 60 { grades.push('B'); } else { grades.push('C'); } } for i in 0..grades.len() { println!("{} got {}!", students[i].name, grades[i]); } }
Solution
struct Student { name: String, marks: u8, } impl Student { fn new(name: &str, marks: u8) -> Self { Self { name: name.to_string(), marks, } } } fn main() { let students = vec![ Student::new("Harry", 75), Student::new("Hermoine", 99), Student::new("Ron", 60), ]; let mut grades = Vec::new(); for student in students.iter() { if student.marks > 80 { grades.push('A'); } else if student.marks > 60 { grades.push('B'); } else { grades.push('C'); } } for i in 0..grades.len() { println!("{} got {}!", students[i].name, grades[i]); } }
Iterating & mutation
// Fix the code so that it compiles. struct Student { name: String, marks: u8, grade: char, } impl Student { fn new(name: &str, marks: u8) -> Self { Self { name: name.to_string(), marks, grade: 'X', } } } fn main() { let students = vec![ Student::new("Harry", 75), Student::new("Hermoine", 99), Student::new("Ron", 60), ]; for student in students { student.grade = if student.marks > 80 { 'A' } else if student.marks > 60 { 'B' } else { 'C' }; } for student in students { println!("{} got {}!", student.name, student.grade); } }
Solution
struct Student { name: String, marks: u8, grade: char, } impl Student { fn new(name: &str, marks: u8) -> Self { Self { name: name.to_string(), marks, grade: 'X', } } } fn main() { let mut students = vec![ Student::new("Harry", 75), Student::new("Hermoine", 99), Student::new("Ron", 60), ]; for student in students.iter_mut() { student.grade = if student.marks > 80 { 'A' } else if student.marks > 60 { 'B' } else { 'C' }; } for student in students { println!("{} got {}!", student.name, student.grade); } }
Project Structure Overview
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Project Structure Overview
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Modules
Additional Resources
Exercises
Visibility
// Something's missing. Fix the code so that it compiles. mod sausage_factory { // Don't let anybody outside of this module see this! fn get_secret_recipe() -> String { String::from("Ginger") } fn make_sausage() { get_secret_recipe(); println!("sausage!"); } } fn main() { sausage_factory::make_sausage(); }
Solution
mod sausage_factory { // Don't let anybody outside of this module see this! fn get_secret_recipe() -> String { String::from("Ginger") } pub fn make_sausage() { get_secret_recipe(); println!("sausage!"); } } fn main() { sausage_factory::make_sausage(); }
Bringing item into scope
// Complete the following code by addressing the TODO. // TODO: Complete this use statement use ??? fn main() { match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()), Err(_) => panic!("SystemTime before UNIX EPOCH!"), } }
Solution
use std::time::SystemTime; fn main() { match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { Ok(n) => println!("1970-01-01 00:00:00 UTC was {} seconds ago!", n.as_secs()), Err(_) => panic!("SystemTime before UNIX EPOCH!"), } }
Multi-file projects 1
// Complete the code by bringing the required items into scope. mod days { pub enum WeekDay { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, } pub fn is_holiday(day: &WeekDay) -> bool { match day { WeekDay::Sunday | WeekDay::Saturday => true, _ => false, } } } fn main() { let today = WeekDay::Friday; if is_holiday(&today) { println!("I can go out!"); } else { println!("I have to work today!"); } }
Solution
use days::{WeekDay, is_holiday}; mod days { pub enum WeekDay { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, } pub fn is_holiday(day: &WeekDay) -> bool { match day { WeekDay::Sunday | WeekDay::Saturday => true, _ => false, } } } fn main() { let today = WeekDay::Friday; if is_holiday(&today) { println!("I can go out!"); } else { println!("I have to work today!"); } }
Multi-file projects 2
// Complete the code by use declarations above main. mod student { pub mod operations { use super::Student; // using super to refer to parent module pub fn assign_grade(student: &mut Student) { if student.marks >= 80 { student.grade = 'A'; } else if student.marks >= 60 { student.grade = 'B'; } else { student.grade = 'C'; } } } pub struct Student { pub name: String, // struct fields can also be made public pub marks: u8, pub grade: char, } impl Student { // make methods/associated functions public in order to access from outside the module pub fn new(name: &str, marks: u8) -> Self { Self { name: name.to_string(), marks, grade: 'X', } } } } fn main() { let mut student = Student::new("Alice", 75); assign_grade(&mut student); println!("{} got {} grade", student.name, student.grade); }
Solution
mod student { pub mod operations { use super::Student; pub fn assign_grade(student: &mut Student) { if student.marks >= 80 { student.grade = 'A'; } else if student.marks >= 60 { student.grade = 'B'; } else { student.grade = 'C'; } } } pub struct Student { pub name: String, pub marks: u8, pub grade: char, } impl Student { pub fn new(name: &str, marks: u8) -> Self { Self { name: name.to_string(), marks, grade: 'X', } } } } use student::{Student, operations::assign_grade}; fn main() { let mut student = Student::new("Alice", 75); assign_grade(&mut student); println!("{} got {} grade", student.name, student.grade); }
Re-exporting
// Make the code compile by addressing the TODO. mod delicious_snacks { // TODO: Fix these use statements use self::fruits::PEAR as ??? use self::veggies::CUCUMBER as ??? mod fruits { // 'static just implies that reference will be valid throughout program execution pub const PEAR: &'static str = "Pear"; pub const APPLE: &'static str = "Apple"; } mod veggies { pub const CUCUMBER: &'static str = "Cucumber"; pub const CARROT: &'static str = "Carrot"; } } fn main() { println!( "favorite snacks: {} and {}", delicious_snacks::fruit, delicious_snacks::veggie ); }
Solution
// Make the code compile by addressing the TODO. mod delicious_snacks { pub use self::fruits::PEAR as fruit; pub use self::veggies::CUCUMBER as veggie; mod fruits { pub const PEAR: &'static str = "Pear"; pub const APPLE: &'static str = "Apple"; } mod veggies { pub const CUCUMBER: &'static str = "Cucumber"; pub const CARROT: &'static str = "Carrot"; } } fn main() { println!( "favorite snacks: {} and {}", delicious_snacks::fruit, delicious_snacks::veggie ); }
External Dependencies
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Publishing your Package
Cargo Features
Exercises
(No exercises yet. Feel free to contribute here!)
Cargo Workspaces
Exercises
(No exercises yet. Feel free to contribute here!)
Unit Tests
Additional Resources
Exercises
Asserting 1
// Make the test compile and pass successfully. #[cfg(test)] mod tests { #[test] fn you_can_assert() { assert!(); } }
Solution
#![allow(unused)] fn main() { #[cfg(test)] mod tests { #[test] fn you_can_assert() { assert!(1 == 1); // use any expression that evaluates to true } } }
Asserting 2
// Make the test compile and pass successfully. #[cfg(test)] mod tests { #[test] fn you_can_assert_eq() { assert_eq!(); } }
Solution
#![allow(unused)] fn main() { #[cfg(test)] mod tests { #[test] fn you_can_assert_eq() { assert_eq!(7+2, 9); // can use any two expressions that are equal } } }
Asserting 3
// Make the tests compile and pass successfully. pub fn is_even(num: i32) -> bool { num % 2 == 0 } #[cfg(test)] mod tests { use super::*; #[test] fn is_true_when_even() { assert!(); } #[test] fn is_false_when_odd() { assert!(); } }
Solution
#![allow(unused)] fn main() { pub fn is_even(num: i32) -> bool { num % 2 == 0 } #[cfg(test)] mod tests { use super::*; #[test] fn is_true_when_even() { assert!(is_even(10)); } #[test] fn is_false_when_odd() { assert!(!is_even(9)); } } }
Returning result
// Complete the test function's signature. fn calculate_sum(nums: &[i32]) -> Result<i32, String> { if nums.len() == 0 { return Err("Number list is empty".to_string()); } let mut sum = 0; for num in nums { sum += num; } Ok(sum) } #[cfg(test)] mod tests { use super::*; #[test] fn calculates_sum_correctly() -> { let nums = [1, 2, 3, 4, 5]; let sum = calculate_sum(&nums)?; assert_eq!(sum, 5 * (5 + 1) / 2); Ok(()) } }
Solution
#![allow(unused)] fn main() { fn calculate_sum(nums: &[i32]) -> Result<i32, String> { if nums.len() == 0 { return Err("Number list is empty".to_string()); } let mut sum = 0; for num in nums { sum += num; } Ok(sum) } #[cfg(test)] mod tests { use super::*; #[test] fn calculates_sum_correctly() -> Result<(), String> { let nums = [1, 2, 3, 4, 5]; let sum = calculate_sum(&nums)?; assert_eq!(sum, 5 * (5 + 1) / 2); Ok(()) } } }
Testing panics
// Add a macro to make the test pass. fn average(nums: &[i32]) -> i32 { if nums.len() == 0 { panic!("Empty number list"); } let mut sum = 0; for num in nums { sum += num; } sum / nums.len() as i32 } #[cfg(test)] mod tests { use super::*; #[test] fn it_panics() { let nums = []; let _avg = average(&nums); } }
Solution
#![allow(unused)] fn main() { fn average(nums: &[i32]) -> i32 { if nums.len() == 0 { panic!("Empty number list"); } let mut sum = 0; for num in nums { sum += num; } sum / nums.len() as i32 } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn it_panics() { let nums = []; let _avg = average(&nums); } } }
Unit Tests
Additional Resources
Exercises
Asserting 1
// Make the test compile and pass successfully. #[cfg(test)] mod tests { #[test] fn you_can_assert() { assert!(); } }
Solution
#![allow(unused)] fn main() { #[cfg(test)] mod tests { #[test] fn you_can_assert() { assert!(1 == 1); // use any expression that evaluates to true } } }
Asserting 2
// Make the test compile and pass successfully. #[cfg(test)] mod tests { #[test] fn you_can_assert_eq() { assert_eq!(); } }
Solution
#![allow(unused)] fn main() { #[cfg(test)] mod tests { #[test] fn you_can_assert_eq() { assert_eq!(7+2, 9); // can use any two expressions that are equal } } }
Asserting 3
// Make the tests compile and pass successfully. pub fn is_even(num: i32) -> bool { num % 2 == 0 } #[cfg(test)] mod tests { use super::*; #[test] fn is_true_when_even() { assert!(); } #[test] fn is_false_when_odd() { assert!(); } }
Solution
#![allow(unused)] fn main() { pub fn is_even(num: i32) -> bool { num % 2 == 0 } #[cfg(test)] mod tests { use super::*; #[test] fn is_true_when_even() { assert!(is_even(10)); } #[test] fn is_false_when_odd() { assert!(!is_even(9)); } } }
Returning result
// Complete the test function's signature. fn calculate_sum(nums: &[i32]) -> Result<i32, String> { if nums.len() == 0 { return Err("Number list is empty".to_string()); } let mut sum = 0; for num in nums { sum += num; } Ok(sum) } #[cfg(test)] mod tests { use super::*; #[test] fn calculates_sum_correctly() -> { let nums = [1, 2, 3, 4, 5]; let sum = calculate_sum(&nums)?; assert_eq!(sum, 5 * (5 + 1) / 2); Ok(()) } }
Solution
#![allow(unused)] fn main() { fn calculate_sum(nums: &[i32]) -> Result<i32, String> { if nums.len() == 0 { return Err("Number list is empty".to_string()); } let mut sum = 0; for num in nums { sum += num; } Ok(sum) } #[cfg(test)] mod tests { use super::*; #[test] fn calculates_sum_correctly() -> Result<(), String> { let nums = [1, 2, 3, 4, 5]; let sum = calculate_sum(&nums)?; assert_eq!(sum, 5 * (5 + 1) / 2); Ok(()) } } }
Testing panics
// Add a macro to make the test pass. fn average(nums: &[i32]) -> i32 { if nums.len() == 0 { panic!("Empty number list"); } let mut sum = 0; for num in nums { sum += num; } sum / nums.len() as i32 } #[cfg(test)] mod tests { use super::*; #[test] fn it_panics() { let nums = []; let _avg = average(&nums); } }
Solution
#![allow(unused)] fn main() { fn average(nums: &[i32]) -> i32 { if nums.len() == 0 { panic!("Empty number list"); } let mut sum = 0; for num in nums { sum += num; } sum / nums.len() as i32 } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn it_panics() { let nums = []; let _avg = average(&nums); } } }
Integration Tests
Exercises
(No exercises yet. Feel free to contribute here!)
Benchmark Tests
Exercises
(No exercises yet. Feel free to contribute here!)
Documentation
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Exercises
(No exercises yet. Feel free to contribute here!)
Generics
Additional Resources
Exercises
Defining generics types 1
// Fix the code by annotating variable with the correct type. fn main() { let mut shopping_list: Vec<?> = Vec::new(); shopping_list.push("milk"); }
Solution
fn main() { let mut shopping_list: Vec<&str> = Vec::new(); shopping_list.push("milk"); }
Defining generics types 2
// Define the generic struct Point. fn main() { let p1 = Point { x: 20, y: 10 }; let p2 = Point { x: 22.3, y: 3.14 }; println!("Point1: ({}, {})", p1.x, p1.y); println!("Point2: ({}, {})", p2.x, p2.y); }
Solution
struct Point<T> { x: T, y: T, } fn main() { let p1 = Point { x: 20, y: 10 }; let p2 = Point { x: 22.3, y: 3.14 }; println!("Point1: ({}, {})", p1.x, p1.y); println!("Point2: ({}, {})", p2.x, p2.y); }
Defining generic types 3
// Make the code compile by defining Operation enum with a single generic type. fn main() { let _op1 = Operation::Add(15u8, 10u8); let _op2 = Operation::Mul(150, 23); let _op3 = Operation::Sub { left: 120, right: 50, }; let _op4 = Operation::Div { dividend: 10.23, divisor: 2.43, }; }
Solution
enum Operation<T> { Add(T, T), Mul(T, T), Sub { left: T, right: T }, Div { dividend: T, divisor: T }, } fn main() { let _op1 = Operation::Add(15u8, 10u8); let _op2 = Operation::Mul(150, 23); let _op3 = Operation::Sub { left: 120, right: 50, }; let _op4 = Operation::Div { dividend: 10.23, divisor: 2.43, }; }
Implementation block 1
// Implement the add method for Pair<i32> type. struct Pair<T>(T, T); fn main() { let p1 = Pair(10, 23); let addition = p1.add(); assert_eq!(addition, 33); }
Solution
use std::ops::Add; struct Pair<T>(T, T); impl<T> Pair<T> where T: Add<Output = T> + Copy { fn add(&self) -> T { self.0 + self.1 } } fn main() { let p1 = Pair(10, 23); let addition = p1.add(); assert_eq!(addition, 33); }
Implementation block 2
// Rewrite Wrapper struct so that it supports wrapping ANY type. struct Wrapper { value: u32, } impl Wrapper { pub fn new(value: u32) -> Self { Wrapper { value } } } #[cfg(test)] mod tests { use super::*; #[test] fn store_u32_in_wrapper() { assert_eq!(Wrapper::new(42).value, 42); } #[test] fn store_str_in_wrapper() { assert_eq!(Wrapper::new("Foo").value, "Foo"); } }
Solution
#![allow(unused)] fn main() { struct Wrapper<T> { value: T, } impl<T> Wrapper<T> { pub fn new(value: T) -> Self { Wrapper { value } } } #[cfg(test)] mod tests { use super::*; #[test] fn store_u32_in_wrapper() { assert_eq!(Wrapper::new(42).value, 42); } #[test] fn store_str_in_wrapper() { assert_eq!(Wrapper::new("Foo").value, "Foo"); } } }
Generic functions
// Fix the code so that it compiles. fn take_and_give_ownership(input: T) -> { input } struct User { name: String, id: u32, } fn main() { let str1 = String::from("Ferris the 🦀!"); let user1 = User { name: "Alice".to_string(), id: 199, }; let _str2 = take_and_give_ownership(str1); let _user2 = take_and_give_ownership(user1); }
Solution
fn take_and_give_ownership<T>(input: T) -> T { input } struct User { name: String, id: u32, } fn main() { let str1 = String::from("Ferris the 🦀!"); let user1 = User { name: "Alice".to_string(), id: 199, }; let _str2 = take_and_give_ownership(str1); let _user2 = take_and_give_ownership(user1); }
Generics
Additional Resources
Exercises
Defining generics types 1
// Fix the code by annotating variable with the correct type. fn main() { let mut shopping_list: Vec<?> = Vec::new(); shopping_list.push("milk"); }
Solution
fn main() { let mut shopping_list: Vec<&str> = Vec::new(); shopping_list.push("milk"); }
Defining generics types 2
// Define the generic struct Point. fn main() { let p1 = Point { x: 20, y: 10 }; let p2 = Point { x: 22.3, y: 3.14 }; println!("Point1: ({}, {})", p1.x, p1.y); println!("Point2: ({}, {})", p2.x, p2.y); }
Solution
struct Point<T> { x: T, y: T, } fn main() { let p1 = Point { x: 20, y: 10 }; let p2 = Point { x: 22.3, y: 3.14 }; println!("Point1: ({}, {})", p1.x, p1.y); println!("Point2: ({}, {})", p2.x, p2.y); }
Defining generic types 3
// Make the code compile by defining Operation enum with a single generic type. fn main() { let _op1 = Operation::Add(15u8, 10u8); let _op2 = Operation::Mul(150, 23); let _op3 = Operation::Sub { left: 120, right: 50, }; let _op4 = Operation::Div { dividend: 10.23, divisor: 2.43, }; }
Solution
enum Operation<T> { Add(T, T), Mul(T, T), Sub { left: T, right: T }, Div { dividend: T, divisor: T }, } fn main() { let _op1 = Operation::Add(15u8, 10u8); let _op2 = Operation::Mul(150, 23); let _op3 = Operation::Sub { left: 120, right: 50, }; let _op4 = Operation::Div { dividend: 10.23, divisor: 2.43, }; }
Implementation block 1
// Implement the add method for Pair<i32> type. struct Pair<T>(T, T); fn main() { let p1 = Pair(10, 23); let addition = p1.add(); assert_eq!(addition, 33); }
Solution
use std::ops::Add; struct Pair<T>(T, T); impl<T> Pair<T> where T: Add<Output = T> + Copy { fn add(&self) -> T { self.0 + self.1 } } fn main() { let p1 = Pair(10, 23); let addition = p1.add(); assert_eq!(addition, 33); }
Implementation block 2
// Rewrite Wrapper struct so that it supports wrapping ANY type. struct Wrapper { value: u32, } impl Wrapper { pub fn new(value: u32) -> Self { Wrapper { value } } } #[cfg(test)] mod tests { use super::*; #[test] fn store_u32_in_wrapper() { assert_eq!(Wrapper::new(42).value, 42); } #[test] fn store_str_in_wrapper() { assert_eq!(Wrapper::new("Foo").value, "Foo"); } }
Solution
#![allow(unused)] fn main() { struct Wrapper<T> { value: T, } impl<T> Wrapper<T> { pub fn new(value: T) -> Self { Wrapper { value } } } #[cfg(test)] mod tests { use super::*; #[test] fn store_u32_in_wrapper() { assert_eq!(Wrapper::new(42).value, 42); } #[test] fn store_str_in_wrapper() { assert_eq!(Wrapper::new("Foo").value, "Foo"); } } }
Generic functions
// Fix the code so that it compiles. fn take_and_give_ownership(input: T) -> { input } struct User { name: String, id: u32, } fn main() { let str1 = String::from("Ferris the 🦀!"); let user1 = User { name: "Alice".to_string(), id: 199, }; let _str2 = take_and_give_ownership(str1); let _user2 = take_and_give_ownership(user1); }
Solution
fn take_and_give_ownership<T>(input: T) -> T { input } struct User { name: String, id: u32, } fn main() { let str1 = String::from("Ferris the 🦀!"); let user1 = User { name: "Alice".to_string(), id: 199, }; let _str2 = take_and_give_ownership(str1); let _user2 = take_and_give_ownership(user1); }
Traits
Additional Resources
Exercises
Implementing traits 1
// Complete the code by addressing the TODO. trait AppendBar { fn append_bar(self) -> Self; } impl AppendBar for String { // TODO: Implement `AppendBar` for type `String`. } fn main() { let s = String::from("Foo"); let s = s.append_bar(); println!("s: {}", s); } #[cfg(test)] mod tests { use super::*; #[test] fn is_foo_bar() { assert_eq!(String::from("Foo").append_bar(), String::from("FooBar")); } #[test] fn is_bar_bar() { assert_eq!( String::from("").append_bar().append_bar(), String::from("BarBar") ); } }
Solution
trait AppendBar { fn append_bar(self) -> Self; } impl AppendBar for String { fn append_bar(self) -> Self { format!("{self}Bar") } } fn main() { let s = String::from("Foo"); let s = s.append_bar(); println!("s: {}", s); } #[cfg(test)] mod tests { use super::*; #[test] fn is_foo_bar() { assert_eq!(String::from("Foo").append_bar(), String::from("FooBar")); } #[test] fn is_bar_bar() { assert_eq!( String::from("").append_bar().append_bar(), String::from("BarBar") ); } }
Implementing traits 2
// Complete the code by addressing the TODO. trait AppendBar { fn append_bar(self) -> Self; } // TODO: Implement trait `AppendBar` for a vector of strings. #[cfg(test)] mod tests { use super::*; #[test] fn is_vec_pop_eq_bar() { let mut foo = vec![String::from("Foo")].append_bar(); assert_eq!(foo.pop().unwrap(), String::from("Bar")); assert_eq!(foo.pop().unwrap(), String::from("Foo")); } }
Solution
#![allow(unused)] fn main() { trait AppendBar { fn append_bar(self) -> Self; } impl AppendBar for Vec<String> { fn append_bar(self) -> Self { let mut res = self; res.push(String::from("Bar")); res } } #[cfg(test)] mod tests { use super::*; #[test] fn is_vec_pop_eq_bar() { let mut foo = vec![String::from("Foo")].append_bar(); assert_eq!(foo.pop().unwrap(), String::from("Bar")); assert_eq!(foo.pop().unwrap(), String::from("Foo")); } } }
Default implementations
// Fix this code by updating the Licensed trait. pub trait Licensed { fn licensing_info(&self) -> String; } struct SomeSoftware { version_number: i32, } struct OtherSoftware { version_number: String, } impl Licensed for SomeSoftware {} // Don't edit this line impl Licensed for OtherSoftware {} // Don't edit this line #[cfg(test)] mod tests { use super::*; #[test] fn is_licensing_info_the_same() { let licensing_info = String::from("Some information"); let some_software = SomeSoftware { version_number: 1 }; let other_software = OtherSoftware { version_number: "v2.0.0".to_string(), }; assert_eq!(some_software.licensing_info(), licensing_info); assert_eq!(other_software.licensing_info(), licensing_info); } }
Solution
#![allow(unused)] fn main() { pub trait Licensed { fn licensing_info(&self) -> String { format!("Some information") } } struct SomeSoftware { version_number: i32, } struct OtherSoftware { version_number: String, } impl Licensed for SomeSoftware {} impl Licensed for OtherSoftware {} #[cfg(test)] mod tests { use super::*; #[test] fn is_licensing_info_the_same() { let licensing_info = String::from("Some information"); let some_software = SomeSoftware { version_number: 1 }; let other_software = OtherSoftware { version_number: "v2.0.0".to_string(), }; assert_eq!(some_software.licensing_info(), licensing_info); assert_eq!(other_software.licensing_info(), licensing_info); } } }
Overriding
// Make the code execute successfully by correctly implementing Message trait for Cat type. trait Message { fn message(&self) -> String { "Default Message!".to_string() } } struct Fish; struct Cat; impl Message for Fish {} impl Message for Cat {} fn main() { let fish = Fish; let cat = Cat; assert_eq!(String::from("Default Message!"), fish.message()); assert_eq!(String::from("Meow 🐱"), cat.message()); }
Solution
trait Message { fn message(&self) -> String { "Default Message!".to_string() } } struct Fish; struct Cat; impl Message for Fish {} impl Message for Cat { fn message(&self) -> String { "Meow 🐱".to_string() } } fn main() { let fish = Fish; let cat = Cat; assert_eq!(String::from("Default Message!"), fish.message()); assert_eq!(String::from("Meow 🐱"), cat.message()); }
Trait Bounds
Additional Resources
Exercises
Specifying trait bounds 1
// Complete the function signatures. trait Message { fn message(&self) -> String { "I love Rust 🦀".to_string() } } fn print_msg1<T:>(input: &T) { println!("{}", input.message()); } fn print_msg2(input:) { println!("{}", input.message()); } fn print_msg3<T>(input: &T) where { println!("{}", input.message()); } struct Dummy; impl Message for Dummy {} fn main() { let var = Dummy; print_msg1(&var); print_msg2(&var); print_msg3(&var); }
Solution
trait Message { fn message(&self) -> String { "I love Rust 🦀".to_string() } } fn print_msg1<T: Message>(input: &T) { println!("{}", input.message()); } fn print_msg2(input: &impl Message) { println!("{}", input.message()); } fn print_msg3<T>(input: &T) where T: Message { println!("{}", input.message()); } struct Dummy; impl Message for Dummy {} fn main() { let var = Dummy; print_msg1(&var); print_msg2(&var); print_msg3(&var); }
Specifying trait bounds 2
// Make the code compile by completing the function signature of `print_message`. trait Message { fn message(&self) -> String { "How are you?".to_string() } } trait Printer { fn print(&self, printable: &impl Message) { println!("Message is: {}", printable.message()); } } struct M; struct P; impl Message for M {} impl Printer for P {} fn print_message<T, U>(msg, printer) where { printer.print(msg); } fn main() { let m = M; let p = P; print_message(&m, &p); }
Solution
trait Message { fn message(&self) -> String { "How are you?".to_string() } } trait Printer { fn print(&self, printable: &impl Message) { println!("Message is: {}", printable.message()); } } struct M; struct P; impl Message for M {} impl Printer for P {} fn print_message<T, U>(msg: &T, printer: &U) where T: Message, U: Printer, { printer.print(msg); } fn main() { let m = M; let p = P; print_message(&m, &p); }
Specifying trait bounds 3
// Complete the code so that it compiles. pub trait Licensed { fn licensing_info(&self) -> String { "some information".to_string() } } struct SomeSoftware {} struct OtherSoftware {} impl Licensed for SomeSoftware {} impl Licensed for OtherSoftware {} // YOU MAY ONLY CHANGE THE NEXT LINE fn compare_license_types(software: ??, software_two: ??) -> bool { software.licensing_info() == software_two.licensing_info() } #[cfg(test)] mod tests { use super::*; #[test] fn compare_license_information() { let some_software = SomeSoftware {}; let other_software = OtherSoftware {}; assert!(compare_license_types(some_software, other_software)); } #[test] fn compare_license_information_backwards() { let some_software = SomeSoftware {}; let other_software = OtherSoftware {}; assert!(compare_license_types(other_software, some_software)); } }
Solution
#![allow(unused)] fn main() { pub trait Licensed { fn licensing_info(&self) -> String { "some information".to_string() } } struct SomeSoftware {} struct OtherSoftware {} impl Licensed for SomeSoftware {} impl Licensed for OtherSoftware {} fn compare_license_types(software: impl Licensed, software_two: impl Licensed) -> bool { software.licensing_info() == software_two.licensing_info() } #[cfg(test)] mod tests { use super::*; #[test] fn compare_license_information() { let some_software = SomeSoftware {}; let other_software = OtherSoftware {}; assert!(compare_license_types(some_software, other_software)); } #[test] fn compare_license_information_backwards() { let some_software = SomeSoftware {}; let other_software = OtherSoftware {}; assert!(compare_license_types(other_software, some_software)); } } }
Multiple trait bounds 1
// Make the code compile by completing the function signature of `get_double_str`. trait Double { fn double(&self) -> Self; } trait Printable { fn convert_to_str(self) -> String; } fn get_double_str(input) -> String { let doubled = input.double(); doubled.convert_to_str() } impl Double for i32 { fn double(&self) -> Self { 2 * self } } impl Printable for i32 { fn convert_to_str(self) -> String { format!("{self}") } } fn main() { let num = 22; let mut msg = format!("{num} doubled is "); msg.push_str(&get_double_str(num)); println!("{msg}"); }
Solution
trait Double { fn double(&self) -> Self; } trait Printable { fn convert_to_str(self) -> String; } fn get_double_str(input: impl Double+Printable) -> String { let doubled = input.double(); doubled.convert_to_str() } impl Double for i32 { fn double(&self) -> Self { 2 * self } } impl Printable for i32 { fn convert_to_str(self) -> String { format!("{self}") } } fn main() { let num = 22; let mut msg = format!("{num} doubled is "); msg.push_str(&get_double_str(num)); println!("{msg}"); }
Multiple trait bounds 2
// Complete the code so that it compiles. pub trait SomeTrait { fn some_function(&self) -> bool { true } } pub trait OtherTrait { fn other_function(&self) -> bool { true } } struct SomeStruct {} struct OtherStruct {} impl SomeTrait for SomeStruct {} impl OtherTrait for SomeStruct {} impl SomeTrait for OtherStruct {} impl OtherTrait for OtherStruct {} // YOU MAY ONLY CHANGE THE NEXT LINE fn some_func(item: ??) -> bool { item.some_function() && item.other_function() } fn main() { some_func(SomeStruct {}); some_func(OtherStruct {}); }
Solution
pub trait SomeTrait { fn some_function(&self) -> bool { true } } pub trait OtherTrait { fn other_function(&self) -> bool { true } } struct SomeStruct {} struct OtherStruct {} impl SomeTrait for SomeStruct {} impl OtherTrait for SomeStruct {} impl SomeTrait for OtherStruct {} impl OtherTrait for OtherStruct {} fn some_func(item: impl SomeTrait+OtherTrait) -> bool { item.some_function() && item.other_function() } fn main() { some_func(SomeStruct {}); some_func(OtherStruct {}); }
Returning trait bounds
// Complete function signature of `get_bigger`. // Only Addable trait's functions should be callable on its return value. trait Addable { fn add_one(&self) -> Self; fn are_equal(a: &Self, b: &Self) -> bool; } impl Addable for u8 { fn add_one(&self) -> Self { if *self == u8::MAX { *self } else { self + 1 } } fn are_equal(a: &Self, b: &Self) -> bool { a == b } } fn get_bigger(a: u8, b: u8) -> { if a > b { a } else { b } } fn main() { let (num1, num2) = (125, 220); let bigger = get_bigger(num1, num2); if Addable::are_equal(&bigger, &bigger.add_one()) { println!("Bigger number has max value") } else { println!("Both numbers are smaller than max value"); } }
Solution
trait Addable { fn add_one(&self) -> Self; fn are_equal(a: &Self, b: &Self) -> bool; } impl Addable for u8 { fn add_one(&self) -> Self { if *self == u8::MAX { *self } else { self + 1 } } fn are_equal(a: &Self, b: &Self) -> bool { a == b } } fn get_bigger(a: u8, b: u8) -> impl Addable { if a > b { a } else { b } } fn main() { let (num1, num2) = (125, 220); let bigger = get_bigger(num1, num2); if Addable::are_equal(&bigger, &bigger.add_one()) { println!("Bigger number has max value") } else { println!("Both numbers are smaller than max value"); } }
Supertraits
Exercises
Implementing supertraits
// Make the code compile. Large should be the default size. /* Default trait is provided by standard library. Has one associated function: default() -> Self */ trait Bounded: Default { fn get_max() -> Self; fn get_min() -> Self; } enum Size { Small, Medium, Large, } impl Bounded for Size { fn get_max() -> Self { Self::Large } fn get_min() -> Self { Self::Small } } fn get_size_num(size: &Size) -> u8 { match size { Size::Small => 0, Size::Medium => 1, Size::Large => 2, } } fn main() { let my_size = Size::Large; let min_size_num = get_size_num(&Size::get_min()); let default_size_num = get_size_num(&Size::default()); let my_size_num = get_size_num(&my_size); if my_size_num == min_size_num { println!("I have the shortest size!"); } if my_size_num == default_size_num { println!("Default size suits me!") } }
Solution
trait Bounded: Default { fn get_max() -> Self; fn get_min() -> Self; } #[derive(Default)] enum Size { Small, Medium, #[default] Large, } impl Bounded for Size { fn get_max() -> Self { Self::Large } fn get_min() -> Self { Self::Small } } fn get_size_num(size: &Size) -> u8 { match size { Size::Small => 0, Size::Medium => 1, Size::Large => 2, } } fn main() { let my_size = Size::Large; let min_size_num = get_size_num(&Size::get_min()); let default_size_num = get_size_num(&Size::default()); let my_size_num = get_size_num(&my_size); if my_size_num == min_size_num { println!("I have the shortest size!"); } if my_size_num == default_size_num { println!("Default size suits me!") } }
Multiple supertraits
// Something is missing with the definition of Comparable trait. Fix it. trait Numeric { fn convert_to_num(&self) -> u8; } trait Printable { fn convert_to_str(&self) -> String; } trait Comparable { fn print_greater(a: &Self, b: &Self) { let num1 = a.convert_to_num(); let num2 = b.convert_to_num(); if num1 > num2 { println!( "{} is greater than {}", a.convert_to_str(), b.convert_to_str() ); } else if num2 > num1 { println!( "{} is greater than {}", b.convert_to_str(), a.convert_to_str() ); } else { println!("Both sizes are {}", a.convert_to_str()); } } } enum Size { Small, Medium, Large, } impl Numeric for Size { fn convert_to_num(&self) -> u8 { match self { Self::Small => 0, Self::Medium => 1, Self::Large => 2, } } } impl Printable for Size { fn convert_to_str(&self) -> String { match self { Self::Small => "Small size".to_string(), Self::Medium => "Medium size".to_string(), Self::Large => "Large size".to_string(), } } } impl Comparable for Size {} fn main() { let (size1, size2) = (Size::Small, Size::Medium); Comparable::print_greater(&size1, &size2); }
Solution
trait Numeric { fn convert_to_num(&self) -> u8; } trait Printable { fn convert_to_str(&self) -> String; } trait Comparable: Numeric + Printable { fn print_greater(a: &Self, b: &Self) { let num1 = a.convert_to_num(); let num2 = b.convert_to_num(); if num1 > num2 { println!( "{} is greater than {}", a.convert_to_str(), b.convert_to_str() ); } else if num2 > num1 { println!( "{} is greater than {}", b.convert_to_str(), a.convert_to_str() ); } else { println!("Both sizes are {}", a.convert_to_str()); } } } enum Size { Small, Medium, Large, } impl Numeric for Size { fn convert_to_num(&self) -> u8 { match self { Self::Small => 0, Self::Medium => 1, Self::Large => 2, } } } impl Printable for Size { fn convert_to_str(&self) -> String { match self { Self::Small => "Small size".to_string(), Self::Medium => "Medium size".to_string(), Self::Large => "Large size".to_string(), } } } impl Comparable for Size {} fn main() { let (size1, size2) = (Size::Small, Size::Medium); Comparable::print_greater(&size1, &size2); }
Trait Objects
Exercises
Returning and passing to functions
// Make the code compile by completing the function signatures. trait Shape { fn shape(&self) -> String; } struct Triangle; struct Square; impl Shape for Triangle { fn shape(&self) -> String { "🔺".to_string() } } impl Shape for Square { fn shape(&self) -> String { "🟥".to_string() } } fn get_shape(side_count: u8) -> { match side_count { 3 => Box::new(Triangle), 4 => Box::new(Square), _ => panic!("No shape with side count available"), } } fn draw_shape(to_draw: &) { println!("{}", to_draw.shape()) } fn main() { let my_shape = get_shape(4); draw_shape(my_shape.as_ref()); }
Solution
trait Shape { fn shape(&self) -> String; } struct Triangle; struct Square; impl Shape for Triangle { fn shape(&self) -> String { "🔺".to_string() } } impl Shape for Square { fn shape(&self) -> String { "🟥".to_string() } } fn get_shape(side_count: u8) -> Box<dyn Shape> { match side_count { 3 => Box::new(Triangle), 4 => Box::new(Square), _ => panic!("No shape with side count available"), } } fn draw_shape(to_draw: &dyn Shape) { println!("{}", to_draw.shape()) } fn main() { let my_shape = get_shape(4); draw_shape(my_shape.as_ref()); }
Trait objects vectors
// Make the code compile by annotating the type for the vector. trait Shape { fn shape(&self) -> String; fn side_count(&self) -> u8; } struct Triangle; struct Square; impl Shape for Triangle { fn shape(&self) -> String { "🔺".to_string() } fn side_count(&self) -> u8 { 3 } } impl Shape for Square { fn shape(&self) -> String { "🟥".to_string() } fn side_count(&self) -> u8 { 4 } } fn main() { let shape1 = Square; let shape2 = Square; let shape3 = Triangle; let shapes = vec![&shape1, &shape2, &shape3]; // fetch the first triangle and print it for shape in shapes { if shape.side_count() == 3 { println!("{}", shape.shape()); break; } } }
Solution
trait Shape { fn shape(&self) -> String; fn side_count(&self) -> u8; } struct Triangle; struct Square; impl Shape for Triangle { fn shape(&self) -> String { "🔺".to_string() } fn side_count(&self) -> u8 { 3 } } impl Shape for Square { fn shape(&self) -> String { "🟥".to_string() } fn side_count(&self) -> u8 { 4 } } fn main() { let shape1 = Square; let shape2 = Square; let shape3 = Triangle; let shapes: Vec<&dyn Shape> = vec![&shape1, &shape2, &shape3]; // fetch the first triangle and print it for shape in shapes { if shape.side_count() == 3 { println!("{}", shape.shape()); break; } } }
Deriving Traits
Additional Resources
Exercises
Deriving on structs
// Complete the code by deriving the required traits. struct Point { x: i32, y: i32, } fn main() { let my_point = Point { x: 20, y: 10 }; let origin = Point::default(); println!("Origin: {origin:?}"); if my_point == origin { println!("Selected point is origin!"); } else { println!("Selected point: {my_point:?}"); } }
Solution
#[derive(Default, Debug, PartialEq)] struct Point { x: i32, y: i32, } fn main() { let my_point = Point { x: 20, y: 10 }; let origin = Point::default(); println!("Origin: {origin:?}"); if my_point == origin { println!("Selected point is origin!"); } else { println!("Selected point: {my_point:?}"); } }
Deriving on enums
// Only one trait needs to be derived. Can you figure out which? enum Size { Small, Medium, Large, } fn main() { let my_size = Size::Small; if my_size == Size::Small { println!("I can fit in any size!"); } }
Solution
#[derive(PartialEq)] enum Size { Small, Medium, Large, } fn main() { let my_size = Size::Small; if my_size == Size::Small { println!("I can fit in any size!"); } }
Orphan Rule
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Exercises
(No exercises yet. Feel free to contribute here!)
Concret Lifetimes
Exercises
Lifetimes of owned values
// Fix the code by shifting only one statement. fn main() { let str1 = "🦀".to_string(); let bytes = str1.into_bytes(); println!("A crab: {str1}"); println!("A crab represented in unicode: {bytes:?}"); }
Solution
fn main() { let str1 = "🦀".to_string(); println!("A crab: {str1}"); let bytes = str1.into_bytes(); println!("A crab represented in unicode: {bytes:?}"); }
Dangling references
// Fix the code by addressing the TODO. fn main() { let num_ref; { // TODO: shift below statement to appropriate location let num = 23; num_ref = # } println!("Reference points to {}", num_ref); }
Solution
fn main() { let num_ref; let num = 23; { num_ref = # } println!("Reference points to {}", num_ref); }
Non-lexical lifetimes
// Fix the code by shifting only one statement. fn main() { let mut my_str = "Old String".to_owned(); let ref1 = &my_str; let ref2 = &mut my_str; ref2.replace_range(0..3, "New"); println!("{ref1}"); println!("{ref2}"); }
Solution
fn main() { let mut my_str = "Old String".to_owned(); let ref1 = &my_str; println!("{ref1}"); let ref2 = &mut my_str; ref2.replace_range(0..3, "New"); println!("{ref2}"); }
Concret Lifetimes
Exercises
Lifetimes of owned values
// Fix the code by shifting only one statement. fn main() { let str1 = "🦀".to_string(); let bytes = str1.into_bytes(); println!("A crab: {str1}"); println!("A crab represented in unicode: {bytes:?}"); }
Solution
fn main() { let str1 = "🦀".to_string(); println!("A crab: {str1}"); let bytes = str1.into_bytes(); println!("A crab represented in unicode: {bytes:?}"); }
Dangling references
// Fix the code by addressing the TODO. fn main() { let num_ref; { // TODO: shift below statement to appropriate location let num = 23; num_ref = # } println!("Reference points to {}", num_ref); }
Solution
fn main() { let num_ref; let num = 23; { num_ref = # } println!("Reference points to {}", num_ref); }
Non-lexical lifetimes
// Fix the code by shifting only one statement. fn main() { let mut my_str = "Old String".to_owned(); let ref1 = &my_str; let ref2 = &mut my_str; ref2.replace_range(0..3, "New"); println!("{ref1}"); println!("{ref2}"); }
Solution
fn main() { let mut my_str = "Old String".to_owned(); let ref1 = &my_str; println!("{ref1}"); let ref2 = &mut my_str; ref2.replace_range(0..3, "New"); println!("{ref2}"); }
Generic Lifetimes
Additional Resources
Exercises
Helping the borrow checker
// Make the code compile by completing the function signature. fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is '{}'", result); }
Solution
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is '{}'", result); }
Complying with the borrow checker
// Make the code compile. You can only shift one statement. // You can not shift variable declarations. fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is '{}'", result); }
Solution
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); println!("The longest string is '{}'", result); } }
Struct lifetimes
// Something is missing from our struct definition. Can you fix it? struct Book { author: &str, title: &str, } fn main() { let name = String::from("Jill Smith"); let title = String::from("Fish Flying"); let book = Book { author: &name, title: &title, }; println!("{} by {}", book.title, book.author); }
Solution
struct Book<'a> { author: &'a str, title: &'a str, } fn main() { let name = String::from("Jill Smith"); let title = String::from("Fish Flying"); let book = Book { author: &name, title: &title, }; println!("{} by {}", book.title, book.author); }
Lifetime Elision
Exercises
Lifetime elision
// The code below executes successfully. However, remove the lifetimes from wherever they can be inferred implicitly. struct NameStack<'a> { names: Vec<&'a str>, } impl<'a> NameStack<'a> { fn new() -> Self { NameStack { names: Vec::new() } } fn add_name(&mut self, name: &'a str) { self.names.push(name); } fn remove_name_with_substr<'b>(&mut self, sub_str: &'b str) -> &'a str { for i in 0..self.names.len() { if self.names[i].contains(sub_str) { let removed = self.names.remove(i); return removed; } } panic!("Name with substring not found"); } } fn main() { let mut my_names = NameStack::new(); my_names.add_name("Alice"); my_names.add_name("Bob"); my_names.add_name("Cindy"); my_names.add_name("Emily"); let removed = my_names.remove_name_with_substr("ice"); println!("Removed: {removed}"); assert_eq!(my_names.names.len(), 3); }
Solution
struct NameStack<'a> { names: Vec<&'a str>, } impl<'a> NameStack<'a> { fn new() -> Self { NameStack { names: Vec::new() } } fn add_name(&mut self, name: &'a str) { self.names.push(name); } fn remove_name_with_substr(&mut self, sub_str: &str) -> &str { for i in 0..self.names.len() { if self.names[i].contains(sub_str) { let removed = self.names.remove(i); return removed; } } panic!("Name with substring not found"); } } fn main() { let mut my_names = NameStack::new(); my_names.add_name("Alice"); my_names.add_name("Bob"); my_names.add_name("Cindy"); my_names.add_name("Emily"); let removed = my_names.remove_name_with_substr("ice"); println!("Removed: {removed}"); assert_eq!(my_names.names.len(), 3); }
Box Smart Pointer
Exercises
Creation
// Initialize heap_var to store value 4 on the heap & make the code execute successfully. fn main() { let stack_var = 5; // TODO: initialize this variable let heap_var let res = stack_var + *heap_var; assert_eq!(res, 9); }
Solution
fn main() { let stack_var = 5; let heap_var = Box::new(4); let res = stack_var + *heap_var; assert_eq!(res, 9); }
Recursive types
Description
This is a helpful description. Read me to understand what to do!
// The recursive type we're implementing in this exercise is the `cons list` - a data structure // frequently found in functional programming languages. Each item in a cons list contains two // elements: the value of the current item and the next item. The last item is a value called `Nil`. // // Step 1: use a `Box` in the enum definition to make the code compile // Step 2: create both empty and non-empty cons lists by replacing `todo!()` #[derive(PartialEq, Debug)] pub enum List { Cons(i32, List), Nil, } fn main() { println!("This is an empty cons list: {:?}", create_empty_list()); println!( "This is a non-empty cons list: {:?}", create_non_empty_list() ); } pub fn create_empty_list() -> List { todo!() } pub fn create_non_empty_list() -> List { todo!() } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_empty_list() { assert_eq!(List::Nil, create_empty_list()) } #[test] fn test_create_non_empty_list() { assert_ne!(create_empty_list(), create_non_empty_list()) } }
Solution
#[derive(PartialEq, Debug)] pub enum List { Cons(i32, Box<List>), Nil, } fn main() { println!("This is an empty cons list: {:?}", create_empty_list()); println!( "This is a non-empty cons list: {:?}", create_non_empty_list() ); } pub fn create_empty_list() -> List { List::Nil } pub fn create_non_empty_list() -> List { List::Cons(1, Box::new(List::Cons(1, Box::new(List::Nil)))) } #[cfg(test)] mod tests { use super::*; #[test] fn test_create_empty_list() { assert_eq!(List::Nil, create_empty_list()) } #[test] fn test_create_non_empty_list() { assert_ne!(create_empty_list(), create_non_empty_list()) } }
Rc Smart Pointer
Exercises
Counting refs 1
// We can get the number of references behind a Rc value using Rc::strong_count. // Can you guess the number of references alive at various places? // Only use numbers as second parameter to assert_eq's. use std::rc::Rc; fn main() { let ptr1 = Rc::new('🦀'); // A crab stored on the heap assert_eq!(Rc::strong_count(&ptr1),); { let ptr2 = Rc::clone(&ptr1); assert_eq!(Rc::strong_count(&ptr1),); { let ptr3 = Rc::clone(&ptr2); assert_eq!(Rc::strong_count(&ptr1),); } assert_eq!(Rc::strong_count(&ptr1),); } assert_eq!(Rc::strong_count(&ptr1),); }
Solution
use std::rc::Rc; fn main() { let ptr1 = Rc::new('🦀'); // A crab stored on the heap assert_eq!(Rc::strong_count(&ptr1), 1); { let ptr2 = Rc::clone(&ptr1); assert_eq!(Rc::strong_count(&ptr1), 2); { let ptr3 = Rc::clone(&ptr2); assert_eq!(Rc::strong_count(&ptr1), 3); } assert_eq!(Rc::strong_count(&ptr1), 2); } assert_eq!(Rc::strong_count(&ptr1), 1); }
Counting refs 2
// In this exercise, we want to express the concept of multiple owners via the Rc<T> type. // This is a model of our solar system - there is a Sun type and multiple Planets. // The Planets take ownership of the sun, indicating that they revolve around the sun. // Make this code compile by using the proper Rc primitives to express that the sun has multiple owners. use std::rc::Rc; #[derive(Debug)] struct Sun {} #[derive(Debug)] enum Planet { Mercury(Rc<Sun>), Venus(Rc<Sun>), Earth(Rc<Sun>), Mars(Rc<Sun>), Jupiter(Rc<Sun>), Saturn(Rc<Sun>), Uranus(Rc<Sun>), Neptune(Rc<Sun>), } impl Planet { fn details(&self) { println!("Hi from {:?}!", self) } } fn main() { let sun = Rc::new(Sun {}); println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference let mercury = Planet::Mercury(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 2 references mercury.details(); let venus = Planet::Venus(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 3 references venus.details(); let earth = Planet::Earth(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 4 references earth.details(); let mars = Planet::Mars(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 5 references mars.details(); let jupiter = Planet::Jupiter(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 6 references jupiter.details(); // TODO let saturn = Planet::Saturn(Rc::new(Sun {})); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references saturn.details(); // TODO let uranus = Planet::Uranus(Rc::new(Sun {})); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references uranus.details(); // TODO let neptune = Planet::Neptune(Rc::new(Sun {})); println!("reference count = {}", Rc::strong_count(&sun)); // 9 references neptune.details(); assert_eq!(Rc::strong_count(&sun), 9); drop(neptune); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references drop(uranus); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references drop(saturn); println!("reference count = {}", Rc::strong_count(&sun)); // 6 references drop(jupiter); println!("reference count = {}", Rc::strong_count(&sun)); // 5 references drop(mars); println!("reference count = {}", Rc::strong_count(&sun)); // 4 references // TODO println!("reference count = {}", Rc::strong_count(&sun)); // 3 references // TODO println!("reference count = {}", Rc::strong_count(&sun)); // 2 references // TODO println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference assert_eq!(Rc::strong_count(&sun), 1); }
Solution
use std::rc::Rc; #[derive(Debug)] struct Sun {} #[derive(Debug)] enum Planet { Mercury(Rc<Sun>), Venus(Rc<Sun>), Earth(Rc<Sun>), Mars(Rc<Sun>), Jupiter(Rc<Sun>), Saturn(Rc<Sun>), Uranus(Rc<Sun>), Neptune(Rc<Sun>), } impl Planet { fn details(&self) { println!("Hi from {:?}!", self) } } fn main() { let sun = Rc::new(Sun {}); println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference let mercury = Planet::Mercury(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 2 references mercury.details(); let venus = Planet::Venus(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 3 references venus.details(); let earth = Planet::Earth(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 4 references earth.details(); let mars = Planet::Mars(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 5 references mars.details(); let jupiter = Planet::Jupiter(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 6 references jupiter.details(); let saturn = Planet::Saturn(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references saturn.details(); let uranus = Planet::Uranus(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references uranus.details(); let neptune = Planet::Neptune(Rc::clone(&sun)); println!("reference count = {}", Rc::strong_count(&sun)); // 9 references neptune.details(); assert_eq!(Rc::strong_count(&sun), 9); drop(neptune); println!("reference count = {}", Rc::strong_count(&sun)); // 8 references drop(uranus); println!("reference count = {}", Rc::strong_count(&sun)); // 7 references drop(saturn); println!("reference count = {}", Rc::strong_count(&sun)); // 6 references drop(jupiter); println!("reference count = {}", Rc::strong_count(&sun)); // 5 references drop(mars); println!("reference count = {}", Rc::strong_count(&sun)); // 4 references drop(earth); println!("reference count = {}", Rc::strong_count(&sun)); // 3 references drop(venus); println!("reference count = {}", Rc::strong_count(&sun)); // 2 references drop(mercury); println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference assert_eq!(Rc::strong_count(&sun), 1); }
RefCell Smart Pointer
Exercises
Borrowing
// Complete the following code. use std::cell::RefCell; fn main() { // storing value 5 on heap let ptr = RefCell::new(5); // get an immutable reference to the stored value let ref1 = ; println!("Stored value: {}", ref1); drop(ref1); // get a mutable reference to the stored value let mut ref2 = ; *ref2 = 6; // Note: we can mutate the value associated with ptr, even though it is not marked as mut println!("Stored value: {}", ref2); }
Solution
use std::cell::RefCell; fn main() { // storing value 5 on heap let ptr = RefCell::new(5); // get an immutable reference to the stored value let ref1 = ptr.borrow(); println!("Stored value: {}", ref1); drop(ref1); // get a mutable reference to the stored value let mut ref2 = ptr.borrow_mut(); *ref2 = 6; // Note: we can mutate the value associated with ptr, even though it is not marked as mut println!("Stored value: {}", ref2); }
Interior mutability
Description
This is a helpful description. Read me to understand what to do!
// Fix the print_details method. You can only modify the method body. use std::cell::RefCell; struct Student { name: String, marks: u8, grade: RefCell<char>, } impl Student { fn new(name: &str, marks: u8) -> Self { Student { name: name.to_owned(), marks, grade: RefCell::new('X'), } } fn print_details(&self) { let grade = match self.marks { 0..=33 => 'C', 34..=60 => 'B', _ => 'A', }; self.grade = grade; println!( "name: {}, marks: {}, grade: {}", self.name, self.marks, self.grade.borrow() ); } } fn main() { let student = Student::new("Harry", 70); student.print_details(); }
Solution
use std::cell::RefCell; struct Student { name: String, marks: u8, grade: RefCell<char>, } impl Student { fn new(name: &str, marks: u8) -> Self { Student { name: name.to_owned(), marks, grade: RefCell::new('X'), } } fn print_details(&self) { let grade = match self.marks { 0..=33 => 'C', 34..=60 => 'B', _ => 'A', }; *self.grade.borrow_mut() = grade; println!( "name: {}, marks: {}, grade: {}", self.name, self.marks, self.grade.borrow() ); } } fn main() { let student = Student::new("Harry", 70); student.print_details(); }
Deref Coercion
Exercises
Deref coercion with Box
// Complete the code by addressing the TODOs. use std::rc::Rc; struct Employee { name: String, id: u32, } impl Employee { fn new(name: &str, id: u32) -> Self { Employee { name: name.to_string(), id, } } fn print_details(&self) { println!("Name: {}, ID: {}", self.name, self.id); } } fn main() { let emp1 = Box::new(Employee::new("Alice", 1234)); // TODO: call print_details on emp1 let emp2 = Box::new(emp1); // TODO: call print_details on emp2 let emp3 = Rc::new(emp2); // TODO: call print_details on emp3 }
Solution
use std::rc::Rc; struct Employee { name: String, id: u32, } impl Employee { fn new(name: &str, id: u32) -> Self { Employee { name: name.to_string(), id, } } fn print_details(&self) { println!("Name: {}, ID: {}", self.name, self.id); } } fn main() { let emp1 = Box::new(Employee::new("Alice", 1234)); emp1.print_details(); let emp2 = Box::new(emp1); emp2.print_details(); let emp3 = Rc::new(emp2); emp3.print_details(); }
Deref & deref mut
// Make the code compile by implementing Deref & DerefMut for Wrapper. use std::ops::{Deref, DerefMut}; struct Wrapper<T>(T); impl<T> Deref for Wrapper<T> { type Target; fn deref(&self) -> &Self::Target {} } impl<T> DerefMut for Wrapper<T> { fn deref_mut(&mut self) -> &mut Self::Target {} } fn main() { let mut my_str = Wrapper(String::from("Ferris")); my_str.push_str(" the crab!!"); my_str.pop(); assert!(are_equal(&my_str, "Ferris the crab!")); } fn are_equal(a: &str, b: &str) -> bool { a == b }
Solution
use std::ops::{Deref, DerefMut}; struct Wrapper<T>(T); impl<T> Deref for Wrapper<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } impl<T> DerefMut for Wrapper<T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } fn main() { let mut my_str = Wrapper(String::from("Ferris")); my_str.push_str(" the crab!!"); my_str.pop(); assert!(are_equal(&my_str, "Ferris the crab!")); } fn are_equal(a: &str, b: &str) -> bool { a == b }
Shared ownership with mutability
// Complete the update_value function. use core::fmt::Debug; use std::cell::RefCell; use std::rc::Rc; fn main() { let owner1 = Rc::new(RefCell::new("Harry")); print_value(&owner1); let owner2 = Rc::clone(&owner1); update_value(&owner2, "Ron"); print_value(&owner1); print_value(&owner2); } fn update_value<T>(owner: &Rc<RefCell<T>>, value: T) {} fn print_value<T: Debug>(owner: &Rc<RefCell<T>>) { println!("{:?}", owner.borrow()); }
Solution
use core::fmt::Debug; use std::cell::RefCell; use std::rc::Rc; fn main() { let owner1 = Rc::new(RefCell::new("Harry")); print_value(&owner1); let owner2 = Rc::clone(&owner1); update_value(&owner2, "Ron"); print_value(&owner1); print_value(&owner2); } fn update_value<T>(owner: &Rc<RefCell<T>>, value: T) { *owner.borrow_mut() = value; } fn print_value<T: Debug>(owner: &Rc<RefCell<T>>) { println!("{:?}", owner.borrow()); }
Unrecoverable Errors
Exercises
Panicking
// Make the program error out with appropriate message where required. fn div(a: i32, b: i32) -> i32 { if b == 0 { // TODO: panic with msg `divide by zero error` } a / b } fn main() { let _res = div(23, 0); }
Solution
fn div(a: i32, b: i32) -> i32 { if b == 0 { panic!("divide by zero error"); } a / b } fn main() { let _res = div(23, 0); }
Unrecoverable Errors
Exercises
Panicking
// Make the program error out with appropriate message where required. fn div(a: i32, b: i32) -> i32 { if b == 0 { // TODO: panic with msg `divide by zero error` } a / b } fn main() { let _res = div(23, 0); }
Solution
fn div(a: i32, b: i32) -> i32 { if b == 0 { panic!("divide by zero error"); } a / b } fn main() { let _res = div(23, 0); }
Recoverable Errors
Exercises
Returning result 1
// Rewrite the find_pos function. // The return type must be Result<usize, Error>. #[derive(Debug, PartialEq)] pub enum Error { EmptyTextOrPattern, // either text or pattern (or both) is empty string TextLenSmall, // text.len() < pattern.len() PatternNotFound, // pattern not a substring of text } // below function finds the starting index of `pattern` in `text` // if `pattern` is not found, it returns text.len() pub fn find_pos(text: &str, pattern: &str) -> usize { let pattern_len = pattern.len(); for start in 0..text.len() - pattern_len + 1 { if &text[start..start + pattern_len] == pattern { return start; } } text.len() } #[cfg(test)] mod tests { use super::*; #[test] fn empty_strings() { assert!(matches!( find_pos("", "pattern"), Err(Error::EmptyTextOrPattern) )); assert!(matches!( find_pos("text", ""), Err(Error::EmptyTextOrPattern) )); assert!(matches!(find_pos("", ""), Err(Error::EmptyTextOrPattern))); } #[test] fn small_text() { assert!(matches!( find_pos("hello", "hello there"), Err(Error::TextLenSmall) )); } #[test] fn pattern_not_present() { assert!(matches!( find_pos("hello", "bye"), Err(Error::PatternNotFound) )); } #[test] fn pattern_present() { assert!(matches!(find_pos("I luv Rust", "uv"), Ok(3))); } }
Solution
#![allow(unused)] fn main() { #[derive(Debug, PartialEq)] pub enum Error { EmptyTextOrPattern, // either text or pattern (or both) is empty string TextLenSmall, // text.len() < pattern.len() PatternNotFound, // pattern not a substring of text } pub fn find_pos(text: &str, pattern: &str) -> Result<usize, Error> { if text.len()==0 || pattern.len()==0 { return Err(Error::EmptyTextOrPattern); } if text.len() < pattern.len() { return Err(Error::TextLenSmall); } let pattern_len = pattern.len(); for start in 0..text.len() - pattern_len + 1 { if &text[start..start + pattern_len] == pattern { return Ok(start); } } Err(Error::PatternNotFound) } #[cfg(test)] mod tests { use super::*; #[test] fn empty_strings() { assert!(matches!( find_pos("", "pattern"), Err(Error::EmptyTextOrPattern) )); assert!(matches!( find_pos("text", ""), Err(Error::EmptyTextOrPattern) )); assert!(matches!(find_pos("", ""), Err(Error::EmptyTextOrPattern))); } #[test] fn small_text() { assert!(matches!( find_pos("hello", "hello there"), Err(Error::TextLenSmall) )); } #[test] fn pattern_not_present() { assert!(matches!( find_pos("hello", "bye"), Err(Error::PatternNotFound) )); } #[test] fn pattern_present() { assert!(matches!(find_pos("I luv Rust", "uv"), Ok(3))); } } }
Returning result 2
// This function refuses to generate text to be printed on a nametag if // you pass it an empty string. It'd be nicer if it explained what the problem // was, instead of just sometimes returning `None`. Thankfully, Rust has a similar // construct to `Option` that can be used to express error conditions. Let's use it! pub fn generate_nametag_text(name: String) -> Option<String> { if name.is_empty() { // Empty names aren't allowed. None } else { Some(format!("Hi! My name is {}", name)) } } #[cfg(test)] mod tests { use super::*; #[test] fn generates_nametag_text_for_a_nonempty_name() { assert_eq!( generate_nametag_text("Beyoncé".into()), Ok("Hi! My name is Beyoncé".into()) ); } #[test] fn explains_why_generating_nametag_text_fails() { assert_eq!( generate_nametag_text("".into()), // Don't change this line Err("`name` was empty; it must be nonempty.".into()) ); } }
Solution
#![allow(unused)] fn main() { pub fn generate_nametag_text(name: String) -> Result<String, String> { if name.is_empty() { Err("`name` was empty; it must be nonempty.".to_string()) } else { Ok(format!("Hi! My name is {}", name)) } } #[cfg(test)] mod tests { use super::*; #[test] fn generates_nametag_text_for_a_nonempty_name() { assert_eq!( generate_nametag_text("Beyoncé".into()), Ok("Hi! My name is Beyoncé".into()) ); } #[test] fn explains_why_generating_nametag_text_fails() { assert_eq!( generate_nametag_text("".into()), // Don't change this line Err("`name` was empty; it must be nonempty.".into()) ); } } }
Returning result 3
// Complete the `new` associated function to check for invalid input. #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); #[derive(PartialEq, Debug)] enum CreationError { Negative, Zero, } impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { // Hmm...? Why is this only returning an Ok value? Ok(PositiveNonzeroInteger(value as u64)) } } #[test] fn test_creation() { assert!(PositiveNonzeroInteger::new(10).is_ok()); assert_eq!( Err(CreationError::Negative), PositiveNonzeroInteger::new(-10) ); assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0)); }
Solution
#![allow(unused)] fn main() { #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); #[derive(PartialEq, Debug)] enum CreationError { Negative, Zero, } impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { if value < 0 { return Err(CreationError::Negative); } else if value == 0 { return Err(CreationError::Zero); } Ok(PositiveNonzeroInteger(value as u64)) } } #[test] fn test_creation() { assert!(PositiveNonzeroInteger::new(10).is_ok()); assert_eq!( Err(CreationError::Negative), PositiveNonzeroInteger::new(-10) ); assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0)); } }
Unwrapping
// Fix the code by addressing the TODO. #[derive(Debug)] struct User { name: String, id: u32, } fn fetch_user(id: u32) -> Result<User, String> { let database = vec![ User { name: "Alice".to_string(), id: 1, }, User { name: "Bob".to_string(), id: 2, }, User { name: "Cindy".to_string(), id: 3, }, ]; for user in database { if user.id == id { return Ok(user); } } Err("User record not present".to_string()) } fn main() { // TODO: `fetch_user` returns a Result type. Add the appropriate method call to extract the User instance and ignore the error case. let user = fetch_user(2); println!("User details: {user:?}"); }
Solution
#[derive(Debug)] struct User { name: String, id: u32, } fn fetch_user(id: u32) -> Result<User, String> { let database = vec![ User { name: "Alice".to_string(), id: 1, }, User { name: "Bob".to_string(), id: 2, }, User { name: "Cindy".to_string(), id: 3, }, ]; for user in database { if user.id == id { return Ok(user); } } Err("User record not present".to_string()) } fn main() { let user = fetch_user(2).unwrap(); println!("User details: {user:?}"); }
Propagating Errors
Exercises
Propagating errors 1
// Fix the evaluate method by propagating the error. // This enum can represent any mathematical expression enum Expression { Val(i32), Add(Box<Expression>, Box<Expression>), Sub(Box<Expression>, Box<Expression>), Mul(Box<Expression>, Box<Expression>), Div(Box<Expression>, Box<Expression>), } use {Expression::Add, Expression::Div, Expression::Mul, Expression::Sub, Expression::Val}; impl Expression { fn evaluate(&self) -> Result<i32, String> { match self { Val(val) => Ok(*val), Add(exp1, exp2) => { let val1 = exp1.evaluate(); let val2 = exp2.evaluate(); Ok(val1 + val2) } Sub(exp1, exp2) => { let val1 = exp1.evaluate(); let val2 = exp2.evaluate(); Ok(val1 - val2) } Mul(exp1, exp2) => { let val1 = exp1.evaluate(); let val2 = exp2.evaluate(); Ok(val1 * val2) } Div(exp1, exp2) => { let val1 = exp1.evaluate(); let val2 = exp2.evaluate(); if val2 == 0 { return Err("Can not divide by zero".to_string()); } Ok(val1 / val2) } } } fn to_boxed(self) -> Box<Self> { Box::new(self) } } fn main() { // calculate: 2 + 3 * 4 let exp = Add( Val(2).to_boxed(), Mul(Val(3).to_boxed(), Val(4).to_boxed()).to_boxed(), ); match exp.evaluate() { Ok(res) => println!("Expression evaluated to: {res}"), Err(error) => println!("Error: {error}"), } }
Solution
enum Expression { Val(i32), Add(Box<Expression>, Box<Expression>), Sub(Box<Expression>, Box<Expression>), Mul(Box<Expression>, Box<Expression>), Div(Box<Expression>, Box<Expression>), } use {Expression::Add, Expression::Div, Expression::Mul, Expression::Sub, Expression::Val}; impl Expression { fn evaluate(&self) -> Result<i32, String> { match self { Val(val) => Ok(*val), Add(exp1, exp2) => { let val1 = exp1.evaluate()?; let val2 = exp2.evaluate()?; Ok(val1 + val2) } Sub(exp1, exp2) => { let val1 = exp1.evaluate()?; let val2 = exp2.evaluate()?; Ok(val1 - val2) } Mul(exp1, exp2) => { let val1 = exp1.evaluate()?; let val2 = exp2.evaluate()?; Ok(val1 * val2) } Div(exp1, exp2) => { let val1 = exp1.evaluate()?; let val2 = exp2.evaluate()?; if val2 == 0 { return Err("Can not divide by zero".to_string()); } Ok(val1 / val2) } } } fn to_boxed(self) -> Box<Self> { Box::new(self) } } fn main() { // calculate: 2 + 3 * 4 let exp = Add( Val(2).to_boxed(), Mul(Val(3).to_boxed(), Val(4).to_boxed()).to_boxed(), ); match exp.evaluate() { Ok(res) => println!("Expression evaluated to: {res}"), Err(error) => println!("Error: {error}"), } }
Propagating errors 2
// Say we're writing a game where you can buy items with tokens. All items cost // 5 tokens, and whenever you purchase items there is a processing fee of 1 // token. A player of the game will type in how many items they want to buy, // and the `total_cost` function will calculate the total number of tokens. // Since the player typed in the quantity, though, we get it as a string-- and // they might have typed anything, not just numbers! // Right now, this function isn't handling the error case at all (and isn't // handling the success case properly either). What we want to do is: // if we call the `parse` function on a string that is not a number, that // function will return a `ParseIntError`, and in that case, we want to // immediately return that error from our function and not try to multiply // and add. use std::num::ParseIntError; pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { let processing_fee = 1; let cost_per_item = 5; let qty = item_quantity.parse::<i32>(); Ok(qty * cost_per_item + processing_fee) } #[cfg(test)] mod tests { use super::*; #[test] fn item_quantity_is_a_valid_number() { assert_eq!(total_cost("34"), Ok(171)); } #[test] fn item_quantity_is_an_invalid_number() { assert_eq!( total_cost("beep boop").unwrap_err().to_string(), "invalid digit found in string" ); } }
Solution
#![allow(unused)] fn main() { use std::num::ParseIntError; pub fn total_cost(item_quantity: &str) -> Result<i32, ParseIntError> { let processing_fee = 1; let cost_per_item = 5; let qty = item_quantity.parse::<i32>()?; Ok(qty * cost_per_item + processing_fee) } #[cfg(test)] mod tests { use super::*; #[test] fn item_quantity_is_a_valid_number() { assert_eq!(total_cost("34"), Ok(171)); } #[test] fn item_quantity_is_an_invalid_number() { assert_eq!( total_cost("beep boop").unwrap_err().to_string(), "invalid digit found in string" ); } } }
Propagating errors 3
// Modify the functions to propagate the error instead of panicking. fn factorial(n: u32) -> Result<u32, String> { if n == 0 { return Ok(1); } else if n > 12 { // Factorial of values > 12 would overflow u32, so return an error return Err(String::from("Input too large")); } let result = n * factorial(n - 1).unwrap(); Ok(result) } fn print_factorial(n: u32) -> Result<(), String> { let result = factorial(n).unwrap(); println!("Factorial of {} is: {}", n, result); Ok(()) } fn main() { let n = 13; if let Err(err) = print_factorial(n) { eprintln!("Error calculating factorial of {}: {}", n, err); } }
Solution
fn factorial(n: u32) -> Result<u32, String> { if n == 0 { return Ok(1); } else if n > 12 { // Factorial of values > 12 would overflow u32, so return an error return Err(String::from("Input too large")); } let result = n * factorial(n - 1)?; Ok(result) } fn print_factorial(n: u32) -> Result<(), String> { let result = factorial(n)?; println!("Factorial of {} is: {}", n, result); Ok(()) } fn main() { let n = 13; if let Err(err) = print_factorial(n) { eprintln!("Error calculating factorial of {}: {}", n, err); } }
Result and Option
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Exercises
Option to Result
// Fix the `fetch_last` function. Do not add any other statement. fn fetch_last<T>(list: &mut Vec<T>) -> Option<T> { list.pop().ok_or() } fn main() { let mut my_nums = Vec::<i32>::new(); match fetch_last(&mut my_nums) { Ok(ele) => println!("Last element: {ele}"), Err(error) => { println!("Error: {error}"); assert_eq!(error, "Empty list".to_owned()); } } }
Solution
fn fetch_last<T>(list: &mut Vec<T>) -> Result<T, String> { list.pop().ok_or("Empty list".to_owned()) } fn main() { let mut my_nums = Vec::<i32>::new(); match fetch_last(&mut my_nums) { Ok(ele) => println!("Last element: {ele}"), Err(error) => { println!("Error: {error}"); assert_eq!(error, "Empty list".to_owned()); } } }
Result to Option
// Use `ok` combinator to convert Result to Option. // Do not add any statements anywhere. fn add(num1: &str, num2: &str) -> Option<u8> { // TODO: only modify the 2 statements below let num1 = num1.parse::<u8>(); let num2 = num2.parse::<u8>(); num1.checked_add(num2) } fn main() { let (num1, num2) = ("4", "5"); if let Some(sum) = add("4", "5") { println!("{num1} + {num2} = {sum}"); } }
Solution
fn add(num1: &str, num2: &str) -> Option<u8> { let num1 = num1.parse::<u8>().ok()?; let num2 = num2.parse::<u8>().ok()?; num1.checked_add(num2) } fn main() { let (num1, num2) = ("4", "5"); if let Some(sum) = add("4", "5") { println!("{num1} + {num2} = {sum}"); } }
Multiple Error Types
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Exercises
Error trait object
// Make the code compile by addressing the TODO. use std::error; use std::fmt; use std::num::ParseIntError; // TODO: update the return type of `main()` to make this compile. fn main() -> Result<(), Box<dyn ???>> { let pretend_user_input = "42"; let x: i64 = pretend_user_input.parse()?; println!("output={:?}", PositiveNonzeroInteger::new(x)?); Ok(()) } // Don't change anything below this line. #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); #[derive(PartialEq, Debug)] enum CreationError { Negative, Zero, } impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { match value { x if x < 0 => Err(CreationError::Negative), x if x == 0 => Err(CreationError::Zero), x => Ok(PositiveNonzeroInteger(x as u64)), } } } // This is required so that `CreationError` can implement `error::Error`. impl fmt::Display for CreationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let description = match *self { CreationError::Negative => "number is negative", CreationError::Zero => "number is zero", }; f.write_str(description) } } impl error::Error for CreationError {}
Solution
use std::error; use std::fmt; use std::num::ParseIntError; fn main() -> Result<(), Box<dyn error::Error>> { let pretend_user_input = "42"; let x: i64 = pretend_user_input.parse()?; println!("output={:?}", PositiveNonzeroInteger::new(x)?); Ok(()) } // Don't change anything below this line. #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); #[derive(PartialEq, Debug)] enum CreationError { Negative, Zero, } impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { match value { x if x < 0 => Err(CreationError::Negative), x if x == 0 => Err(CreationError::Zero), x => Ok(PositiveNonzeroInteger(x as u64)), } } } // This is required so that `CreationError` can implement `error::Error`. impl fmt::Display for CreationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let description = match *self { CreationError::Negative => "number is negative", CreationError::Zero => "number is zero", }; f.write_str(description) } } impl error::Error for CreationError {}
Mutiple errors 1
// Make the code compile by completing the add function. use std::num::ParseIntError; enum AddError { ParseError(ParseIntError), OverFlow, } // parse input strings to u8's and return their sum fn add(num1: &str, num2: &str) -> Result { let num1 = num1.parse::<u8>().map_err(|e| AddError::ParseError(e))?; let num2 = num2.parse::<u8>().map_err(|e| AddError::ParseError(e))?; // ok_or() transforms Option<T> -> Result<T,E> and takes value of type E as input let sum = num1.checked_add(num2).ok_or()?; Ok(sum) } fn main() { let (user_input1, user_input2) = ("23", "45"); match add(user_input1, user_input2) { Ok(sum) => println!("Sum = {sum}"), Err(e) => match e { AddError::OverFlow => println!("Sum > {}", u8::MAX), AddError::ParseError(e) => println!("Invalid input, parse error: {e:?}"), }, } }
Solution
use std::num::ParseIntError; enum AddError { ParseError(ParseIntError), OverFlow, } // parse input strings to u8's and return their sum fn add(num1: &str, num2: &str) -> Result<u8, AddError> { let num1 = num1.parse::<u8>().map_err(|e| AddError::ParseError(e))?; let num2 = num2.parse::<u8>().map_err(|e| AddError::ParseError(e))?; // ok_or() transforms Option<T> -> Result<T,E> and takes value of type E as input let sum = num1.checked_add(num2).ok_or(AddError::OverFlow)?; Ok(sum) } fn main() { let (user_input1, user_input2) = ("23", "45"); match add(user_input1, user_input2) { Ok(sum) => println!("Sum = {sum}"), Err(e) => match e { AddError::OverFlow => println!("Sum > {}", u8::MAX), AddError::ParseError(e) => println!("Invalid input, parse error: {e:?}"), }, } }
Multiple errors 2
// Complete the code by addressing the TODOs. use std::num::ParseIntError; // This is a custom error type that we will be using in `parse_pos_nonzero()`. #[derive(PartialEq, Debug)] enum ParsePosNonzeroError { Creation(CreationError), ParseInt(ParseIntError), } impl ParsePosNonzeroError { fn from_creation(err: CreationError) -> ParsePosNonzeroError { ParsePosNonzeroError::Creation(err) } // TODO: add another error conversion function here. // fn from_parseint... } fn parse_pos_nonzero(s: &str) -> Result<PositiveNonzeroInteger, ParsePosNonzeroError> { // TODO: change this to return an appropriate error instead of panicking // when `parse()` returns an error. let x: i64 = s.parse().unwrap(); PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation) } // Don't change anything below this line. #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); #[derive(PartialEq, Debug)] enum CreationError { Negative, Zero, } impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { match value { x if x < 0 => Err(CreationError::Negative), x if x == 0 => Err(CreationError::Zero), x => Ok(PositiveNonzeroInteger(x as u64)), } } } #[cfg(test)] mod test { use super::*; #[test] fn test_parse_error() { // We can't construct a ParseIntError, so we have to pattern match. assert!(matches!( parse_pos_nonzero("not a number"), Err(ParsePosNonzeroError::ParseInt(_)) )); } #[test] fn test_negative() { assert_eq!( parse_pos_nonzero("-555"), Err(ParsePosNonzeroError::Creation(CreationError::Negative)) ); } #[test] fn test_zero() { assert_eq!( parse_pos_nonzero("0"), Err(ParsePosNonzeroError::Creation(CreationError::Zero)) ); } #[test] fn test_positive() { let x = PositiveNonzeroInteger::new(42); assert!(x.is_ok()); assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap())); } }
Solution
#![allow(unused)] fn main() { use std::num::ParseIntError; // This is a custom error type that we will be using in `parse_pos_nonzero()`. #[derive(PartialEq, Debug)] enum ParsePosNonzeroError { Creation(CreationError), ParseInt(ParseIntError), } impl ParsePosNonzeroError { fn from_creation(err: CreationError) -> ParsePosNonzeroError { ParsePosNonzeroError::Creation(err) } fn from_parseint(err: ParseIntError) -> ParsePosNonzeroError { ParsePosNonzeroError::ParseInt(err) } } fn parse_pos_nonzero(s: &str) -> Result<PositiveNonzeroInteger, ParsePosNonzeroError> { let x: i64 = s.parse().map_err(ParsePosNonzeroError::from_parseint)?; PositiveNonzeroInteger::new(x).map_err(ParsePosNonzeroError::from_creation) } // Don't change anything below this line. #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); #[derive(PartialEq, Debug)] enum CreationError { Negative, Zero, } impl PositiveNonzeroInteger { fn new(value: i64) -> Result<PositiveNonzeroInteger, CreationError> { match value { x if x < 0 => Err(CreationError::Negative), x if x == 0 => Err(CreationError::Zero), x => Ok(PositiveNonzeroInteger(x as u64)), } } } #[cfg(test)] mod test { use super::*; #[test] fn test_parse_error() { // We can't construct a ParseIntError, so we have to pattern match. assert!(matches!( parse_pos_nonzero("not a number"), Err(ParsePosNonzeroError::ParseInt(_)) )); } #[test] fn test_negative() { assert_eq!( parse_pos_nonzero("-555"), Err(ParsePosNonzeroError::Creation(CreationError::Negative)) ); } #[test] fn test_zero() { assert_eq!( parse_pos_nonzero("0"), Err(ParsePosNonzeroError::Creation(CreationError::Zero)) ); } #[test] fn test_positive() { let x = PositiveNonzeroInteger::new(42); assert!(x.is_ok()); assert_eq!(parse_pos_nonzero("42"), Ok(x.unwrap())); } } }
Avoiding Unwrap
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Exercises
Avoiding unwrap
// Modify `get_last` to handle all cases and make the program execute successfully. fn get_last(nums: &mut Vec<i32>) -> i32 { nums.pop().unwrap() } fn main() { let mut vec1 = vec![1, 2, 3]; let mut vec2 = vec![]; assert!(matches!(get_last(&mut vec1), Ok(3))); assert!(matches!(get_last(&mut vec2), Err("Empty vector"))); }
Solution
fn get_last(nums: &mut Vec<i32>) -> Result<i32, &str> { if nums.len() == 0 { return Err("Empty vector"); } Ok(nums.pop().unwrap()) } fn main() { let mut vec1 = vec![1, 2, 3]; let mut vec2 = vec![]; assert!(matches!(get_last(&mut vec1), Ok(3))); assert!(matches!(get_last(&mut vec2), Err("Empty vector"))); }
Avoiding Unwrap
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Exercises
Avoiding unwrap
// Modify `get_last` to handle all cases and make the program execute successfully. fn get_last(nums: &mut Vec<i32>) -> i32 { nums.pop().unwrap() } fn main() { let mut vec1 = vec![1, 2, 3]; let mut vec2 = vec![]; assert!(matches!(get_last(&mut vec1), Ok(3))); assert!(matches!(get_last(&mut vec2), Err("Empty vector"))); }
Solution
fn get_last(nums: &mut Vec<i32>) -> Result<i32, &str> { if nums.len() == 0 { return Err("Empty vector"); } Ok(nums.pop().unwrap()) } fn main() { let mut vec1 = vec![1, 2, 3]; let mut vec2 = vec![]; assert!(matches!(get_last(&mut vec1), Ok(3))); assert!(matches!(get_last(&mut vec2), Err("Empty vector"))); }
Custom Errors
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Exercises
Creating custom errors
// Complete the `factorial` function. #[derive(Debug)] enum Error { Overflow, InvalidInput, } fn factorial(num: i32) -> Result<i32, Error> { /* if num < 0 return InvalidInput error if num > 12 return Overflow error if num < 2 return num else return num * result of factorial num - 1 */ } fn main() { assert!(matches!(factorial(-12), Err(Error::InvalidInput))); assert!(matches!(factorial(20), Err(Error::Overflow))); assert!(matches!(factorial(5), Ok(120))); }
Solution
#[derive(Debug)] enum Error { Overflow, InvalidInput, } fn factorial(num: i32) -> Result<i32, Error> { if num < 0 { return Err(Error::InvalidInput); } else if num > 12 { return Err(Error::Overflow); } let res = if num < 2 { num } else { num * factorial(num-1)? }; Ok(res) } fn main() { assert!(matches!(factorial(-12), Err(Error::InvalidInput))); assert!(matches!(factorial(20), Err(Error::Overflow))); assert!(matches!(factorial(5), Ok(120))); }
Converting errors 1
// Complete the code by implementing `From<ParseIntError>` for `AddError`. use std::num::ParseIntError; #[derive(Debug)] enum AddError { ParseError(ParseIntError), Overflow, } impl From<ParseIntError> for AddError {} fn add(num1: &str, num2: &str) -> Result<u8, AddError> { let num1 = num1.parse::<u8>()?; let num2 = num2.parse::<u8>()?; num1.checked_add(num2).ok_or(AddError::Overflow) } fn main() { let (input1, input2) = ("23", "50"); match add(input1, input2) { Ok(res) => println!("{input1} + {input2} = {res}"), Err(e) => println!("Error: {e:?}"), } }
Solution
use std::num::ParseIntError; #[derive(Debug)] enum AddError { ParseError(ParseIntError), Overflow, } impl From<ParseIntError> for AddError { fn from(value: ParseIntError) -> Self { Self::ParseError(value) } } fn add(num1: &str, num2: &str) -> Result<u8, AddError> { let num1 = num1.parse::<u8>()?; let num2 = num2.parse::<u8>()?; num1.checked_add(num2).ok_or(AddError::Overflow) } fn main() { let (input1, input2) = ("23", "50"); match add(input1, input2) { Ok(res) => println!("{input1} + {input2} = {res}"), Err(e) => println!("Error: {e:?}"), } }
Converting errors 2
// Complete the `add` function by converting from ParseIntError to AddError using `map_err` combinator. use std::num::ParseIntError; #[derive(Debug)] enum AddError { ParseError(ParseIntError), Overflow, } fn add(num1: &str, num2: &str) -> Result<u8, AddError> { let num1 = num1.parse::<u8>()?; let num2 = num2.parse::<u8>()?; num1.checked_add(num2).ok_or(AddError::Overflow) } fn main() { let (input1, input2) = ("23", "50"); match add(input1, input2) { Ok(res) => println!("{input1} + {input2} = {res}"), Err(e) => println!("Error: {e:?}"), } }
Solution
use std::num::ParseIntError; #[derive(Debug)] enum AddError { ParseError(ParseIntError), Overflow, } fn add(num1: &str, num2: &str) -> Result<u8, AddError> { let num1 = num1.parse::<u8>().map_err(|e| AddError::ParseError(e))?; let num2 = num2.parse::<u8>().map_err(|e| AddError::ParseError(e))?; num1.checked_add(num2).ok_or(AddError::Overflow) } fn main() { let (input1, input2) = ("23", "50"); match add(input1, input2) { Ok(res) => println!("{input1} + {input2} = {res}"), Err(e) => println!("Error: {e:?}"), } }
Error trait 1
// Complete the code and make the tests pass by implementing std::error::Error for CalculationError pub enum CalculationError { InvalidOperation(char), InvalidOperand(String), DivideByZero { dividend: i8 }, Overflow, } pub fn calculate(num1: &str, num2: &str, operation: char) -> Result<i8, CalculationError> { let num1 = num1 .parse::<i8>() .map_err(|_| CalculationError::InvalidOperand(num1.to_owned()))?; let num2 = num2 .parse::<i8>() .map_err(|_| CalculationError::InvalidOperand(num2.to_owned()))?; match operation { '+' => num1.checked_add(num2).ok_or(CalculationError::Overflow), '-' => num1.checked_sub(num2).ok_or(CalculationError::Overflow), '*' => num1.checked_mul(num2).ok_or(CalculationError::Overflow), '/' => { if num2 == 0 { return Err(CalculationError::DivideByZero { dividend: num1 }); } num1.checked_div(num2).ok_or(CalculationError::Overflow) } _ => Err(CalculationError::InvalidOperation(operation)), } } #[cfg(test)] mod tests { use super::*; struct Error { e: Box<dyn std::error::Error>, } impl Error { // presence of this method ensures that std::error::Error is satisfied for CalculationError fn new(err: CalculationError) -> Self { Self { e: Box::new(err) } } } #[test] fn invalid_operation() { let res1 = calculate("12", "20", '$'); let res2 = calculate("45", "43", '^'); match (res1, res2) { (Err(e1), Err(e2)) => { assert_eq!( "$ is not a valid operation. Allowed: +,-,/,*", format!("{}", e1) ); assert_eq!( "^ is not a valid operation. Allowed: +,-,/,*", format!("{}", e2) ); } _ => panic!("Error expected!"), } } #[test] fn invalid_operand() { let res1 = calculate("ab", "3r", '+'); let res2 = calculate("45", "4.23", '^'); match (res1, res2) { (Err(e1), Err(e2)) => { assert_eq!( "ab is not a valid integer in range [-128, 127]", format!("{}", e1) ); assert_eq!( "4.23 is not a valid integer in range [-128, 127]", format!("{}", e2) ); } _ => panic!("Error expected!"), } } #[test] fn divide_by_zero() { let res1 = calculate("45", "0", '/'); let res2 = calculate("70", "0", '/'); match (res1, res2) { (Err(e1), Err(e2)) => { assert_eq!( "Can not divide by zero. Attempting to divide 45 by 0", format!("{}", e1) ); assert_eq!( "Can not divide by zero. Attempting to divide 70 by 0", format!("{}", e2) ); } _ => panic!("Error expected!"), } } #[test] fn overflow() { let res = calculate("120", "120", '+'); match res { Err(e) => { assert_eq!("Overflow while performing the operation", format!("{}", e)); } _ => panic!("Error expected!"), } } }
Solution
#![allow(unused)] fn main() { use std::error::Error; use std::fmt::Display; #[derive(Debug)] pub enum CalculationError { InvalidOperation(char), InvalidOperand(String), DivideByZero { dividend: i8 }, Overflow, } impl Display for CalculationError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let msg = match self { Self::InvalidOperation(op) => format!("{op} is not a valid operation. Allowed: +,-,/,*"), Self::InvalidOperand(num) => format!("{num} is not a valid integer in range [-128, 127]"), Self::DivideByZero { dividend } => format!("Can not divide by zero. Attempting to divide {dividend} by 0"), Self::Overflow => format!("Overflow while performing the operation"), }; f.write_str(&msg) } } impl Error for CalculationError { fn source(&self) -> Option<&(dyn Error + 'static)> { None } } pub fn calculate(num1: &str, num2: &str, operation: char) -> Result<i8, CalculationError> { let num1 = num1 .parse::<i8>() .map_err(|_| CalculationError::InvalidOperand(num1.to_owned()))?; let num2 = num2 .parse::<i8>() .map_err(|_| CalculationError::InvalidOperand(num2.to_owned()))?; match operation { '+' => num1.checked_add(num2).ok_or(CalculationError::Overflow), '-' => num1.checked_sub(num2).ok_or(CalculationError::Overflow), '*' => num1.checked_mul(num2).ok_or(CalculationError::Overflow), '/' => { if num2 == 0 { return Err(CalculationError::DivideByZero { dividend: num1 }); } num1.checked_div(num2).ok_or(CalculationError::Overflow) } _ => Err(CalculationError::InvalidOperation(operation)), } } #[cfg(test)] mod tests { use super::*; struct Error { e: Box<dyn std::error::Error>, } impl Error { // presence of this method ensures that std::error::Error is satisfied for CalculationError fn new(err: CalculationError) -> Self { Self { e: Box::new(err) } } } #[test] fn invalid_operation() { let res1 = calculate("12", "20", '$'); let res2 = calculate("45", "43", '^'); match (res1, res2) { (Err(e1), Err(e2)) => { assert_eq!( "$ is not a valid operation. Allowed: +,-,/,*", format!("{}", e1) ); assert_eq!( "^ is not a valid operation. Allowed: +,-,/,*", format!("{}", e2) ); } _ => panic!("Error expected!"), } } #[test] fn invalid_operand() { let res1 = calculate("ab", "3r", '+'); let res2 = calculate("45", "4.23", '^'); match (res1, res2) { (Err(e1), Err(e2)) => { assert_eq!( "ab is not a valid integer in range [-128, 127]", format!("{}", e1) ); assert_eq!( "4.23 is not a valid integer in range [-128, 127]", format!("{}", e2) ); } _ => panic!("Error expected!"), } } #[test] fn divide_by_zero() { let res1 = calculate("45", "0", '/'); let res2 = calculate("70", "0", '/'); match (res1, res2) { (Err(e1), Err(e2)) => { assert_eq!( "Can not divide by zero. Attempting to divide 45 by 0", format!("{}", e1) ); assert_eq!( "Can not divide by zero. Attempting to divide 70 by 0", format!("{}", e2) ); } _ => panic!("Error expected!"), } } #[test] fn overflow() { let res = calculate("120", "120", '+'); match res { Err(e) => { assert_eq!("Overflow while performing the operation", format!("{}", e)); } _ => panic!("Error expected!"), } } } }
Error trait 2
// Complete the implementation of `Display` & `Debug` for `MonthError`. use std::{error::Error, fmt::{Display, Debug}}; enum Month { Jan, Feb, March, April, May, June, July, Aug, Sept, Oct, Nov, Dec, } impl TryFrom<u8> for Month { type Error = MonthError; fn try_from(value: u8) -> Result<Self, Self::Error> { match value { 1 => Ok(Month::Jan), 2 => Ok(Month::Feb), 3 => Ok(Month::March), 4 => Ok(Month::April), 5 => Ok(Month::May), 6 => Ok(Month::June), 7 => Ok(Month::July), 8 => Ok(Month::Aug), 9 => Ok(Month::Sept), 10 => Ok(Month::Oct), 11 => Ok(Month::Nov), 12 => Ok(Month::Dec), _ => Err(MonthError { source: None, msg: format!("{value} does not correspond to a month") }) } } } impl ToString for Month { fn to_string(&self) -> String { match self { Self::Jan => "Jan", Self::Feb => "Feb", Self::March => "March", Self::April => "April", Self::May => "May", Self::June => "June", Self::July => "July", Self::Aug => "Aug", Self::Sept => "Sept", Self::Oct => "Oct", Self::Nov => "Nov", Self::Dec => "Dec" }.to_string() } } struct MonthError { source: Option<Box<dyn Error>>, msg: String, } impl Error for MonthError { fn source(&self) -> Option<&(dyn Error + 'static)> { self.source.as_deref() } } impl Display for MonthError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // write "Error converting input to months, check your input" } } impl Debug for MonthError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // if self.source is Some, write "{a}\nCaused by: {b:?}", a=self.msg, b=error stored inside Some // else, write self.msg to the Formatter } } // function to convert a string to corresponding vector of owned month strings // e.g. "1 2 3 4" -> ["Jan", "Feb", "March", "April"] fn get_months(months: &str) -> Result<Vec<String>, MonthError> { let nums = months.split(' ') .into_iter() .map(|num_str| num_str.parse::<u8>() .map_err(|e| MonthError { source: Some(Box::new(e)), msg: format!("Can not parse {num_str} to u8") })) .collect::<Result<Vec<u8>, _>>() .map_err(|e| MonthError { source: Some(Box::new(e)), msg: format!("Could not convert string to numbers") })?; let month_strs = nums.into_iter() .map(|num| Month::try_from(num)) .collect::<Result<Vec<Month>, _>>() .map_err(|e| MonthError { source: Some(Box::new(e)), msg: format!("Could not convert nums to months") })? .into_iter() .map(|month| month.to_string()) .collect::<Vec<String>>(); Ok(month_strs) } fn convert_and_print(nums: &str) { match get_months(nums) { Ok(months) => println!("Months: {months:?}\n"), Err(e) => println!("{e:?}\n"), } } fn main() { let input1 = "1 2 3 4 9 10"; let input2 = "xyz 10 12"; let input3 = "1 3 20"; convert_and_print(input1); convert_and_print(input2); convert_and_print(input3); }
Solution
use std::{error::Error, fmt::{Display, Debug}}; enum Month { Jan, Feb, March, April, May, June, July, Aug, Sept, Oct, Nov, Dec, } impl TryFrom<u8> for Month { type Error = MonthError; fn try_from(value: u8) -> Result<Self, Self::Error> { match value { 1 => Ok(Month::Jan), 2 => Ok(Month::Feb), 3 => Ok(Month::March), 4 => Ok(Month::April), 5 => Ok(Month::May), 6 => Ok(Month::June), 7 => Ok(Month::July), 8 => Ok(Month::Aug), 9 => Ok(Month::Sept), 10 => Ok(Month::Oct), 11 => Ok(Month::Nov), 12 => Ok(Month::Dec), _ => Err(MonthError { source: None, msg: format!("{value} does not correspond to a month") }) } } } impl ToString for Month { fn to_string(&self) -> String { match self { Self::Jan => "Jan", Self::Feb => "Feb", Self::March => "March", Self::April => "April", Self::May => "May", Self::June => "June", Self::July => "July", Self::Aug => "Aug", Self::Sept => "Sept", Self::Oct => "Oct", Self::Nov => "Nov", Self::Dec => "Dec" }.to_string() } } struct MonthError { source: Option<Box<dyn Error>>, msg: String, } impl Error for MonthError { fn source(&self) -> Option<&(dyn Error + 'static)> { self.source.as_deref() } } impl Display for MonthError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("Error converting input to months, check your input") } } impl Debug for MonthError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(err) = &self.source { f.write_fmt(format_args!("{}\nCaused by: {:?}", self.msg, err)) } else { f.write_str(&self.msg) } } } // function to convert a string to corresponding vector of owned month strings // e.g. "1 2 3 4" -> ["Jan", "Feb", "March", "April"] fn get_months(months: &str) -> Result<Vec<String>, MonthError> { let nums = months.split(' ') .into_iter() .map(|num_str| num_str.parse::<u8>() .map_err(|e| MonthError { source: Some(Box::new(e)), msg: format!("Can not parse {num_str} to u8") })) .collect::<Result<Vec<u8>, _>>() .map_err(|e| MonthError { source: Some(Box::new(e)), msg: format!("Could not convert string to numbers") })?; let month_strs = nums.into_iter() .map(|num| Month::try_from(num)) .collect::<Result<Vec<Month>, _>>() .map_err(|e| MonthError { source: Some(Box::new(e)), msg: format!("Could not convert nums to months") })? .into_iter() .map(|month| month.to_string()) .collect::<Vec<String>>(); Ok(month_strs) } fn convert_and_print(nums: &str) { match get_months(nums) { Ok(months) => println!("Months: {months:?}\n"), Err(e) => println!("{e:?}\n"), } } fn main() { let input1 = "1 2 3 4 9 10"; let input2 = "xyz 10 12"; let input3 = "1 3 20"; convert_and_print(input1); convert_and_print(input2); convert_and_print(input3); }
thiserror
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Exercises
(No exercises yet. Feel free to contribute here!)
anyhow
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Exercises
(No exercises yet. Feel free to contribute here!)
error-stack
Exercises
(No exercises yet. Feel free to contribute here!)
Closures
Exercises
Defining closures
// Define the `double` closure & make the code execute successfully. fn main() { let double = ; assert_eq!(double(5), 10); assert_eq!(double(-10), -20); }
Solution
fn main() { let double = |x| 2*x; assert_eq!(double(5), 10); assert_eq!(double(-10), -20); }
Struct fields
// Complete the struct definition & the implementation block. struct BinaryOperation<T, U> where T: Copy, { operand1: T, operand2: T, operation: U, } impl<T, U> BinaryOperation<T, U> where T: Copy, { fn calculate(&self) -> T {} } fn main() { let multiply = BinaryOperation { operand1: 20, operand2: 6, operation: |a, b| a * b, }; let divide = BinaryOperation { operand1: 22.0, operand2: 7.0, operation: |x, y| x / y, }; println!( "{} x {} = {}", multiply.operand1, multiply.operand2, multiply.calculate() ); println!( "{} / {} = {}", divide.operand1, divide.operand2, divide.calculate() ); }
Solution
struct BinaryOperation<T, U> where T: Copy, U: Fn(T, T) -> T { operand1: T, operand2: T, operation: U, } impl<T, U> BinaryOperation<T, U> where T: Copy, U: Fn(T, T) -> T { fn calculate(&self) -> T { (self.operation)(self.operand1, self.operand2) } } fn main() { let multiply = BinaryOperation { operand1: 20, operand2: 6, operation: |a, b| a * b, }; let divide = BinaryOperation { operand1: 22.0, operand2: 7.0, operation: |x, y| x / y, }; println!( "{} x {} = {}", multiply.operand1, multiply.operand2, multiply.calculate() ); println!( "{} / {} = {}", divide.operand1, divide.operand2, divide.calculate() ); }
Mutably capturing environment
// Something is wrong with the struct definition. Can you fix it? struct Manipulator<T> where T: Fn(), { operation: T, } impl<T> Manipulator<T> where T: Fn(), { fn manipulate(&mut self) { (self.operation)() } } fn main() { let mut x = 0; let mut y = 100; let mut x_manipulator = Manipulator { operation: || x += 1, }; let mut y_manipulator = Manipulator { operation: || y /= 5, }; x_manipulator.manipulate(); x_manipulator.manipulate(); y_manipulator.manipulate(); y_manipulator.manipulate(); assert_eq!(x, 2); assert_eq!(y, 4); }
Solution
struct Manipulator<T> where T: FnMut(), { operation: T, } impl<T> Manipulator<T> where T: FnMut(), { fn manipulate(&mut self) { (self.operation)() } } fn main() { let mut x = 0; let mut y = 100; let mut x_manipulator = Manipulator { operation: || x += 1, }; let mut y_manipulator = Manipulator { operation: || y /= 5, }; x_manipulator.manipulate(); x_manipulator.manipulate(); y_manipulator.manipulate(); y_manipulator.manipulate(); assert_eq!(x, 2); assert_eq!(y, 4); }
Moving into closures
// Make the code compile by addressing the TODO. fn main() { let my_str = "Hi there!".to_owned(); let substr = "here"; let check_substr = move |sub: &str| my_str.contains(sub); let res = check_substr(substr); // TODO: shift the statement below to somewhere else println!("String: {my_str}"); println!("String contains {substr} : {res}"); }
Solution
fn main() { let my_str = "Hi there!".to_owned(); let substr = "here"; println!("String: {my_str}"); let check_substr = move |sub: &str| my_str.contains(sub); let res = check_substr(substr); println!("String contains {substr} : {res}"); }
Passing to functions
// Complete the function signature. fn average<T, U>(nums: &[i32], add: T, div: U) -> f32 where { let mut sum = 0; for num in nums { sum = add(sum, *num); } div(sum, nums.len() as i32) } fn main() { let add = |a, b| a + b; let div = |a, b| a as f32 / b as f32; let my_nums = [1, 2, 3, 4, 5]; let res = average(&my_nums, add, div); println!("Average of {my_nums:?} = {res}"); }
Solution
fn average<T, U>(nums: &[i32], add: T, div: U) -> f32 where T: Fn(i32, i32) -> i32, U: Fn(i32, i32) -> f32, { let mut sum = 0; for num in nums { sum = add(sum, *num); } div(sum, nums.len() as i32) } fn main() { let add = |a, b| a + b; let div = |a, b| a as f32 / b as f32; let my_nums = [1, 2, 3, 4, 5]; let res = average(&my_nums, add, div); println!("Average of {my_nums:?} = {res}"); }
Returning from functions 1
// Fix the code to make it compile. fn main() { let adder1 = get_adder(-2); let adder2 = get_adder(100); assert_eq!(adder1(20), 18); assert_eq!(adder2(10), 110); } fn get_adder(to_add: i32) -> impl Fn(i32) -> i32 { |x| x + to_add }
Solution
fn main() { let adder1 = get_adder(-2); let adder2 = get_adder(100); assert_eq!(adder1(20), 18); assert_eq!(adder2(10), 110); } fn get_adder(to_add: i32) -> impl Fn(i32) -> i32 { move |x| x + to_add }
Returning to functions 2
// Fix the code to make it compile. enum Operation { Add, Sub, Mul, } fn get_implementation(operation: Operation, operand2: i32) -> impl Fn(i32) -> i32 { match operation { Operation::Add => move |x| x + operand2, Operation::Sub => move |x| x - operand2, Operation::Mul => move |x| x * operand2, } } fn main() { const OPERAND2: i32 = 5; let adder = get_implementation(Operation::Add, OPERAND2); let multiplier = get_implementation(Operation::Mul, OPERAND2); assert_eq!(adder(10), 15); assert_eq!(multiplier(10), 50); }
Solution
enum Operation { Add, Sub, Mul, } fn get_implementation(operation: Operation, operand2: i32) -> Box<dyn Fn(i32) -> i32> { match operation { Operation::Add => Box::new(move |x| x + operand2), Operation::Sub => Box::new(move |x| x - operand2), Operation::Mul => Box::new(move |x| x * operand2), } } fn main() { const OPERAND2: i32 = 5; let adder = get_implementation(Operation::Add, OPERAND2); let multiplier = get_implementation(Operation::Mul, OPERAND2); assert_eq!(adder(10), 15); assert_eq!(multiplier(10), 50); }
Closures
Exercises
Defining closures
// Define the `double` closure & make the code execute successfully. fn main() { let double = ; assert_eq!(double(5), 10); assert_eq!(double(-10), -20); }
Solution
fn main() { let double = |x| 2*x; assert_eq!(double(5), 10); assert_eq!(double(-10), -20); }
Struct fields
// Complete the struct definition & the implementation block. struct BinaryOperation<T, U> where T: Copy, { operand1: T, operand2: T, operation: U, } impl<T, U> BinaryOperation<T, U> where T: Copy, { fn calculate(&self) -> T {} } fn main() { let multiply = BinaryOperation { operand1: 20, operand2: 6, operation: |a, b| a * b, }; let divide = BinaryOperation { operand1: 22.0, operand2: 7.0, operation: |x, y| x / y, }; println!( "{} x {} = {}", multiply.operand1, multiply.operand2, multiply.calculate() ); println!( "{} / {} = {}", divide.operand1, divide.operand2, divide.calculate() ); }
Solution
struct BinaryOperation<T, U> where T: Copy, U: Fn(T, T) -> T { operand1: T, operand2: T, operation: U, } impl<T, U> BinaryOperation<T, U> where T: Copy, U: Fn(T, T) -> T { fn calculate(&self) -> T { (self.operation)(self.operand1, self.operand2) } } fn main() { let multiply = BinaryOperation { operand1: 20, operand2: 6, operation: |a, b| a * b, }; let divide = BinaryOperation { operand1: 22.0, operand2: 7.0, operation: |x, y| x / y, }; println!( "{} x {} = {}", multiply.operand1, multiply.operand2, multiply.calculate() ); println!( "{} / {} = {}", divide.operand1, divide.operand2, divide.calculate() ); }
Mutably capturing environment
// Something is wrong with the struct definition. Can you fix it? struct Manipulator<T> where T: Fn(), { operation: T, } impl<T> Manipulator<T> where T: Fn(), { fn manipulate(&mut self) { (self.operation)() } } fn main() { let mut x = 0; let mut y = 100; let mut x_manipulator = Manipulator { operation: || x += 1, }; let mut y_manipulator = Manipulator { operation: || y /= 5, }; x_manipulator.manipulate(); x_manipulator.manipulate(); y_manipulator.manipulate(); y_manipulator.manipulate(); assert_eq!(x, 2); assert_eq!(y, 4); }
Solution
struct Manipulator<T> where T: FnMut(), { operation: T, } impl<T> Manipulator<T> where T: FnMut(), { fn manipulate(&mut self) { (self.operation)() } } fn main() { let mut x = 0; let mut y = 100; let mut x_manipulator = Manipulator { operation: || x += 1, }; let mut y_manipulator = Manipulator { operation: || y /= 5, }; x_manipulator.manipulate(); x_manipulator.manipulate(); y_manipulator.manipulate(); y_manipulator.manipulate(); assert_eq!(x, 2); assert_eq!(y, 4); }
Moving into closures
// Make the code compile by addressing the TODO. fn main() { let my_str = "Hi there!".to_owned(); let substr = "here"; let check_substr = move |sub: &str| my_str.contains(sub); let res = check_substr(substr); // TODO: shift the statement below to somewhere else println!("String: {my_str}"); println!("String contains {substr} : {res}"); }
Solution
fn main() { let my_str = "Hi there!".to_owned(); let substr = "here"; println!("String: {my_str}"); let check_substr = move |sub: &str| my_str.contains(sub); let res = check_substr(substr); println!("String contains {substr} : {res}"); }
Passing to functions
// Complete the function signature. fn average<T, U>(nums: &[i32], add: T, div: U) -> f32 where { let mut sum = 0; for num in nums { sum = add(sum, *num); } div(sum, nums.len() as i32) } fn main() { let add = |a, b| a + b; let div = |a, b| a as f32 / b as f32; let my_nums = [1, 2, 3, 4, 5]; let res = average(&my_nums, add, div); println!("Average of {my_nums:?} = {res}"); }
Solution
fn average<T, U>(nums: &[i32], add: T, div: U) -> f32 where T: Fn(i32, i32) -> i32, U: Fn(i32, i32) -> f32, { let mut sum = 0; for num in nums { sum = add(sum, *num); } div(sum, nums.len() as i32) } fn main() { let add = |a, b| a + b; let div = |a, b| a as f32 / b as f32; let my_nums = [1, 2, 3, 4, 5]; let res = average(&my_nums, add, div); println!("Average of {my_nums:?} = {res}"); }
Returning from functions 1
// Fix the code to make it compile. fn main() { let adder1 = get_adder(-2); let adder2 = get_adder(100); assert_eq!(adder1(20), 18); assert_eq!(adder2(10), 110); } fn get_adder(to_add: i32) -> impl Fn(i32) -> i32 { |x| x + to_add }
Solution
fn main() { let adder1 = get_adder(-2); let adder2 = get_adder(100); assert_eq!(adder1(20), 18); assert_eq!(adder2(10), 110); } fn get_adder(to_add: i32) -> impl Fn(i32) -> i32 { move |x| x + to_add }
Returning to functions 2
// Fix the code to make it compile. enum Operation { Add, Sub, Mul, } fn get_implementation(operation: Operation, operand2: i32) -> impl Fn(i32) -> i32 { match operation { Operation::Add => move |x| x + operand2, Operation::Sub => move |x| x - operand2, Operation::Mul => move |x| x * operand2, } } fn main() { const OPERAND2: i32 = 5; let adder = get_implementation(Operation::Add, OPERAND2); let multiplier = get_implementation(Operation::Mul, OPERAND2); assert_eq!(adder(10), 15); assert_eq!(multiplier(10), 50); }
Solution
enum Operation { Add, Sub, Mul, } fn get_implementation(operation: Operation, operand2: i32) -> Box<dyn Fn(i32) -> i32> { match operation { Operation::Add => Box::new(move |x| x + operand2), Operation::Sub => Box::new(move |x| x - operand2), Operation::Mul => Box::new(move |x| x * operand2), } } fn main() { const OPERAND2: i32 = 5; let adder = get_implementation(Operation::Add, OPERAND2); let multiplier = get_implementation(Operation::Mul, OPERAND2); assert_eq!(adder(10), 15); assert_eq!(multiplier(10), 50); }
Function Pointers
Exercises
As parameters
// Complete the function signature for `factorial`. It must not contain any generics/traits. fn decrement(x: u32) -> u32 { x - 1 } fn multiply(x: u32, y: u32) -> u32 { x * y } fn factorial(num, dec, mul) { let mut res = 1; let mut temp = num; while temp > 1 { res = mul(res, temp); temp = dec(temp); } res } fn main() { let num = 6; let fact = factorial(num, decrement, multiply); println!("{num}! = {fact}"); }
Solution
fn decrement(x: u32) -> u32 { x - 1 } fn multiply(x: u32, y: u32) -> u32 { x * y } fn factorial(num: u32, dec: fn(u32)->u32, mul: fn(u32, u32)->u32) -> u32 { let mut res = 1; let mut temp = num; while temp > 1 { res = mul(res, temp); temp = dec(temp); } res } fn main() { let num = 6; let fact = factorial(num, decrement, multiply); println!("{num}! = {fact}"); }
Coercing from closures
// Fix the code by addressing the TODO. fn invoker<T>(logic: fn(T), arg: T) { logic(arg); } fn main() { // TODO: shift below declaration to somewhere else let greeting = String::from("Nice to meet you"); let greet = |name| { println!("{greeting} {name}!"); }; invoker(greet, "Jenny"); }
Solution
fn invoker<T>(logic: fn(T), arg: T) { logic(arg); } fn main() { let greet = |name| { let greeting = String::from("Nice to meet you"); println!("{greeting} {name}!"); }; invoker(greet, "Jenny"); }
Iterator Pattern
Exercises
Iterator trait
// Make the code compile by implementing the Iterator trait for `Queue`. struct Queue { items: Vec<i32>, } impl Queue { fn new(items: Vec<i32>) -> Self { Self { items } } } impl Iterator for Queue { type Item; fn next(&mut self) -> Option<Self::Item> {} } fn main() { let mut queue = Queue::new(vec![3, 2, 1]); assert!(matches!(queue.next(), Some(3))); assert!(matches!(queue.next(), Some(2))); assert!(matches!(queue.next(), Some(1))); assert!(matches!(queue.next(), None)); }
Solution
struct Queue { items: Vec<i32>, } impl Queue { fn new(items: Vec<i32>) -> Self { Self { items } } } impl Iterator for Queue { type Item = i32; fn next(&mut self) -> Option<Self::Item> { if self.items.len() > 0 { Some(self.items.remove(0)) } else { None } } } fn main() { let mut queue = Queue::new(vec![3, 2, 1]); assert!(matches!(queue.next(), Some(3))); assert!(matches!(queue.next(), Some(2))); assert!(matches!(queue.next(), Some(1))); assert!(matches!(queue.next(), None)); }
Into iterator 1
// Provide the trait implementations and make the code execute successfully. struct Employee { first_name: String, last_name: String, id: String, } struct EmployeeIter { state: Vec<String>, } impl Iterator for EmployeeIter { type Item; fn next(&mut self) -> Option<Self::Item> {} } impl IntoIterator for Employee { type Item; type IntoIter; fn into_iter(self) -> Self::IntoIter {} } fn main() { let employee = Employee { first_name: "Alice".to_owned(), last_name: "Smith".to_owned(), id: "ab123".to_owned(), }; let mut emp_iter = employee.into_iter(); println!("First name: {}", emp_iter.next().unwrap()); println!("Last name: {}", emp_iter.next().unwrap()); println!("ID: {}", emp_iter.next().unwrap()); assert_eq!(emp_iter.next(), None); }
Solution
struct Employee { first_name: String, last_name: String, id: String, } struct EmployeeIter { state: Vec<String>, } impl Iterator for EmployeeIter { type Item = String; fn next(&mut self) -> Option<Self::Item> { self.state.pop() } } impl IntoIterator for Employee { type Item = String; type IntoIter = EmployeeIter; fn into_iter(self) -> Self::IntoIter { EmployeeIter { state: vec![ self.id, self.last_name, self.first_name, ] } } } fn main() { let employee = Employee { first_name: "Alice".to_owned(), last_name: "Smith".to_owned(), id: "ab123".to_owned(), }; let mut emp_iter = employee.into_iter(); println!("First name: {}", emp_iter.next().unwrap()); println!("Last name: {}", emp_iter.next().unwrap()); println!("ID: {}", emp_iter.next().unwrap()); assert_eq!(emp_iter.next(), None); }
Into iterator 2
// Fix the code by completing the into_iter() method. struct Employee { first_name: String, last_name: String, id: String, } impl IntoIterator for Employee { type Item = String; type IntoIter = std::vec::IntoIter<String>; fn into_iter(self) -> Self::IntoIter { vec![ format!("First name: {}", self.first_name), // do the same for last_name & id ] } } fn main() { let employee = Employee { first_name: "Alice".to_owned(), last_name: "Smith".to_owned(), id: "ab123".to_owned(), }; println!("Employee Details:"); for detail in employee { println!("{detail}"); } }
Solution
struct Employee { first_name: String, last_name: String, id: String, } impl IntoIterator for Employee { type Item = String; type IntoIter = std::vec::IntoIter<String>; fn into_iter(self) -> Self::IntoIter { vec![ format!("First name: {}", self.first_name), format!("Last name: {}", self.last_name), format!("ID: {}", self.id), ].into_iter() } } fn main() { let employee = Employee { first_name: "Alice".to_owned(), last_name: "Smith".to_owned(), id: "ab123".to_owned(), }; println!("Employee Details:"); for detail in employee { println!("{detail}"); } }
Iterator methods
// In this exercise, you'll learn some of the unique advantages that iterators // can offer. Follow the steps to complete the exercise. // Step 1. // Complete the `capitalize_first` function. // "hello" -> "Hello" pub fn capitalize_first(input: &str) -> String { let mut c = input.chars(); match c.next() { None => String::new(), Some(first) => ???, } } // Step 2. // Apply the `capitalize_first` function to a slice of string slices. // Return a vector of strings. // ["hello", "world"] -> ["Hello", "World"] pub fn capitalize_words_vector(words: &[&str]) -> Vec<String> { vec![] } // Step 3. // Apply the `capitalize_first` function again to a slice of string slices. // Return a single string. // ["hello", " ", "world"] -> "Hello World" pub fn capitalize_words_string(words: &[&str]) -> String { String::new() } #[cfg(test)] mod tests { use super::*; #[test] fn test_success() { assert_eq!(capitalize_first("hello"), "Hello"); } #[test] fn test_empty() { assert_eq!(capitalize_first(""), ""); } #[test] fn test_iterate_string_vec() { let words = vec!["hello", "world"]; assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]); } #[test] fn test_iterate_into_string() { let words = vec!["hello", " ", "world"]; assert_eq!(capitalize_words_string(&words), "Hello World"); } }
Solution
#![allow(unused)] fn main() { pub fn capitalize_first(input: &str) -> String { let mut c = input.chars(); match c.next() { None => String::new(), Some(first) => format!("{}{}", first.to_uppercase(), c.as_str()), } } pub fn capitalize_words_vector(words: &[&str]) -> Vec<String> { words.iter().map(|w| capitalize_first(w)).collect() } pub fn capitalize_words_string(words: &[&str]) -> String { words.iter().map(|w| capitalize_first(w)).collect::<Vec<String>>().join("") } #[cfg(test)] mod tests { use super::*; #[test] fn test_success() { assert_eq!(capitalize_first("hello"), "Hello"); } #[test] fn test_empty() { assert_eq!(capitalize_first(""), ""); } #[test] fn test_iterate_string_vec() { let words = vec!["hello", "world"]; assert_eq!(capitalize_words_vector(&words), ["Hello", "World"]); } #[test] fn test_iterate_into_string() { let words = vec!["hello", " ", "world"]; assert_eq!(capitalize_words_string(&words), "Hello World"); } } }
Iterator Over Collections
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Exercises
Iterating immutably
// Make the code compile by filling in the `???`s fn main() { let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"]; let mut my_iterable_fav_fruits = ???; // TODO: Step 1 assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana")); assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 2 assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado")); assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 3 assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry")); assert_eq!(my_iterable_fav_fruits.next(), ???); // TODO: Step 4 }
Solution
fn main() { let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"]; let mut my_iterable_fav_fruits = my_fav_fruits.iter(); assert_eq!(my_iterable_fav_fruits.next(), Some(&"banana")); assert_eq!(my_iterable_fav_fruits.next(), Some(&"custard apple")); assert_eq!(my_iterable_fav_fruits.next(), Some(&"avocado")); assert_eq!(my_iterable_fav_fruits.next(), Some(&"peach")); assert_eq!(my_iterable_fav_fruits.next(), Some(&"raspberry")); assert_eq!(my_iterable_fav_fruits.next(), None); }
Iterating mutably
// Make the code compile by only modifying the loop. fn main() { let mut nums = [0, 1, 2, 3, 4]; let odd_nums = [1, 3, 5, 7, 9]; for num in nums.iter() { *num = 2 * *num + 1; } assert_eq!(nums, odd_nums) }
Solution
fn main() { let mut nums = [0, 1, 2, 3, 4]; let odd_nums = [1, 3, 5, 7, 9]; for num in nums.iter_mut() { *num = 2 * *num + 1; } assert_eq!(nums, odd_nums) }
Hashmaps
// Fix the code to make it compile. use std::collections::HashMap; fn main() { // marks scored out of 50 let mut marks = HashMap::from([("Harry", 40.0), ("Hermoine", 50.0), ("Ron", 35.5)]); // convert marks into percentage for (_, marks) in marks { *marks = (*marks * 100.0) / 50.0; } marks.for_each(|(student, marks)| println!("{student} scored {marks}%")); }
Solution
use std::collections::HashMap; fn main() { // marks scored out of 50 let mut marks = HashMap::from([("Harry", 40.0), ("Hermoine", 50.0), ("Ron", 35.5)]); // convert marks into percentage for (_, marks) in marks.iter_mut() { *marks = (*marks * 100.0) / 50.0; } marks.iter().for_each(|(student, marks)| println!("{student} scored {marks}%")); }
Combinators
Exercises
Combinators 1
// 1. Complete the divide function to get the first four tests to pass. // 2. Get the remaining tests to pass by completing the result_with_list and // list_of_results functions. #[derive(Debug, PartialEq, Eq)] pub enum DivisionError { NotDivisible(NotDivisibleError), DivideByZero, } #[derive(Debug, PartialEq, Eq)] pub struct NotDivisibleError { dividend: i32, divisor: i32, } // Calculate `a` divided by `b` if `a` is evenly divisible by `b`. // Otherwise, return a suitable error. pub fn divide(a: i32, b: i32) -> Result<i32, DivisionError> { todo!(); } // Complete the function and return a value of the correct type so the test passes. // Desired output: Ok([1, 11, 1426, 3]) fn result_with_list() -> () { let numbers = vec![27, 297, 38502, 81]; let division_results = numbers.into_iter().map(|n| divide(n, 27)); } // Complete the function and return a value of the correct type so the test passes. // Desired output: [Ok(1), Ok(11), Ok(1426), Ok(3)] fn list_of_results() -> () { let numbers = vec![27, 297, 38502, 81]; let division_results = numbers.into_iter().map(|n| divide(n, 27)); } #[cfg(test)] mod tests { use super::*; #[test] fn test_success() { assert_eq!(divide(81, 9), Ok(9)); } #[test] fn test_not_divisible() { assert_eq!( divide(81, 6), Err(DivisionError::NotDivisible(NotDivisibleError { dividend: 81, divisor: 6 })) ); } #[test] fn test_divide_by_0() { assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero)); } #[test] fn test_divide_0_by_something() { assert_eq!(divide(0, 81), Ok(0)); } #[test] fn test_result_with_list() { assert_eq!(format!("{:?}", result_with_list()), "Ok([1, 11, 1426, 3])"); } #[test] fn test_list_of_results() { assert_eq!( format!("{:?}", list_of_results()), "[Ok(1), Ok(11), Ok(1426), Ok(3)]" ); } }
Solution
#![allow(unused)] fn main() { #[derive(Debug, PartialEq, Eq)] pub enum DivisionError { NotDivisible(NotDivisibleError), DivideByZero, } #[derive(Debug, PartialEq, Eq)] pub struct NotDivisibleError { dividend: i32, divisor: i32, } pub fn divide(a: i32, b: i32) -> Result<i32, DivisionError> { if b == 0 { return Err(DivisionError::DivideByZero); } else if a % b != 0 { return Err(DivisionError::NotDivisible(NotDivisibleError { dividend: a, divisor: b })); } Ok(a/b) } fn result_with_list() -> Result<Vec<i32>, DivisionError> { let numbers = vec![27, 297, 38502, 81]; let division_results = numbers.into_iter().map(|n| divide(n, 27)); let mut nums = vec![]; for res in division_results { nums.push(res?); } Ok(nums) } fn list_of_results() -> Vec<Result<i32, DivisionError>> { let numbers = vec![27, 297, 38502, 81]; let division_results = numbers.into_iter().map(|n| divide(n, 27)).collect(); division_results } #[cfg(test)] mod tests { use super::*; #[test] fn test_success() { assert_eq!(divide(81, 9), Ok(9)); } #[test] fn test_not_divisible() { assert_eq!( divide(81, 6), Err(DivisionError::NotDivisible(NotDivisibleError { dividend: 81, divisor: 6 })) ); } #[test] fn test_divide_by_0() { assert_eq!(divide(81, 0), Err(DivisionError::DivideByZero)); } #[test] fn test_divide_0_by_something() { assert_eq!(divide(0, 81), Ok(0)); } #[test] fn test_result_with_list() { assert_eq!(format!("{:?}", result_with_list()), "Ok([1, 11, 1426, 3])"); } #[test] fn test_list_of_results() { assert_eq!( format!("{:?}", list_of_results()), "[Ok(1), Ok(11), Ok(1426), Ok(3)]" ); } } }
Combinators 2
// Let's define a simple model to track bootcamp exercise progress. Progress // will be modelled using a hash map. The name of the exercise is the key and // the progress is the value. Two counting functions were created to count the // number of exercises with a given progress. These counting functions use // imperative style for loops. Recreate this counting functionality using // iterators. Only the two iterator methods (count_iterator and // count_collection_iterator) need to be modified. // Make the code compile and the tests pass. use std::collections::HashMap; #[derive(Clone, Copy, PartialEq, Eq)] enum Progress { None, Some, Complete, } fn count_for(map: &HashMap<String, Progress>, value: Progress) -> usize { let mut count = 0; for val in map.values() { if val == &value { count += 1; } } count } fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize { // map is a hashmap with String keys and Progress values. // map = { "variables1": Complete, "from_str": None, ... } todo!(); } fn count_collection_for(collection: &[HashMap<String, Progress>], value: Progress) -> usize { let mut count = 0; for map in collection { for val in map.values() { if val == &value { count += 1; } } } count } fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize { // collection is a slice of hashmaps. // collection = [{ "variables1": Complete, "from_str": None, ... }, // { "variables2": Complete, ... }, ... ] todo!(); } #[cfg(test)] mod tests { use super::*; #[test] fn count_complete() { let map = get_map(); assert_eq!(3, count_iterator(&map, Progress::Complete)); } #[test] fn count_equals_for() { let map = get_map(); assert_eq!( count_for(&map, Progress::Complete), count_iterator(&map, Progress::Complete) ); } #[test] fn count_collection_complete() { let collection = get_vec_map(); assert_eq!( 6, count_collection_iterator(&collection, Progress::Complete) ); } #[test] fn count_collection_equals_for() { let collection = get_vec_map(); assert_eq!( count_collection_for(&collection, Progress::Complete), count_collection_iterator(&collection, Progress::Complete) ); } fn get_map() -> HashMap<String, Progress> { use Progress::*; let mut map = HashMap::new(); map.insert(String::from("variables1"), Complete); map.insert(String::from("functions1"), Complete); map.insert(String::from("hashmap1"), Complete); map.insert(String::from("arc1"), Some); map.insert(String::from("as_ref_mut"), None); map.insert(String::from("from_str"), None); map } fn get_vec_map() -> Vec<HashMap<String, Progress>> { use Progress::*; let map = get_map(); let mut other = HashMap::new(); other.insert(String::from("variables2"), Complete); other.insert(String::from("functions2"), Complete); other.insert(String::from("if1"), Complete); other.insert(String::from("from_into"), None); other.insert(String::from("try_from_into"), None); vec![map, other] } }
Solution
#![allow(unused)] fn main() { use std::collections::HashMap; #[derive(Clone, Copy, PartialEq, Eq)] enum Progress { None, Some, Complete, } fn count_for(map: &HashMap<String, Progress>, value: Progress) -> usize { let mut count = 0; for val in map.values() { if val == &value { count += 1; } } count } fn count_iterator(map: &HashMap<String, Progress>, value: Progress) -> usize { map.values().map(|v| if v==&value { 1 } else { 0 }).sum() } fn count_collection_for(collection: &[HashMap<String, Progress>], value: Progress) -> usize { let mut count = 0; for map in collection { for val in map.values() { if val == &value { count += 1; } } } count } fn count_collection_iterator(collection: &[HashMap<String, Progress>], value: Progress) -> usize { collection.iter().map(|h| count_iterator(h, value)).sum() } #[cfg(test)] mod tests { use super::*; #[test] fn count_complete() { let map = get_map(); assert_eq!(3, count_iterator(&map, Progress::Complete)); } #[test] fn count_equals_for() { let map = get_map(); assert_eq!( count_for(&map, Progress::Complete), count_iterator(&map, Progress::Complete) ); } #[test] fn count_collection_complete() { let collection = get_vec_map(); assert_eq!( 6, count_collection_iterator(&collection, Progress::Complete) ); } #[test] fn count_collection_equals_for() { let collection = get_vec_map(); assert_eq!( count_collection_for(&collection, Progress::Complete), count_collection_iterator(&collection, Progress::Complete) ); } fn get_map() -> HashMap<String, Progress> { use Progress::*; let mut map = HashMap::new(); map.insert(String::from("variables1"), Complete); map.insert(String::from("functions1"), Complete); map.insert(String::from("hashmap1"), Complete); map.insert(String::from("arc1"), Some); map.insert(String::from("as_ref_mut"), None); map.insert(String::from("from_str"), None); map } fn get_vec_map() -> Vec<HashMap<String, Progress>> { use Progress::*; let map = get_map(); let mut other = HashMap::new(); other.insert(String::from("variables2"), Complete); other.insert(String::from("functions2"), Complete); other.insert(String::from("if1"), Complete); other.insert(String::from("from_into"), None); other.insert(String::from("try_from_into"), None); vec![map, other] } } }
Intro to Concurrency
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Intro to Concurrency
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Creating Threads
Exercises
Joining 1
// Modify the code to ensure that main thread waits for other threads to finish. use std::thread; fn main() { // print hello 10 times from spawned thread let handle = thread::spawn(|| { for i in 0..10 { println!("{i} Hello from spawned thread!") } }); // print hello 10 times from main thread for i in 0..10 { println!("{i} Hello from main thread!"); } }
Solution
use std::thread; fn main() { // print hello 10 times from spawned thread let handle = thread::spawn(|| { for i in 0..10 { println!("{i} Hello from spawned thread!") } }); // print hello 10 times from main thread for i in 0..10 { println!("{i} Hello from main thread!"); } handle.join().unwrap(); }
Joining 2
// Fix the code to make sure that sum is calculated correctly. use std::{ ops::Range, thread::{self, JoinHandle}, }; fn summation_thread(range: Range<u64>) -> JoinHandle<u64> { thread::spawn(|| { let mut sum = 0; for num in range { sum += num; } sum }) } // calculate the sum of numbers 1..=40_00_000 using four threads fn main() { let handle1 = summation_thread(1..10_00_000); let handle2 = summation_thread(10_00_000..20_00_000); let handle3 = summation_thread(20_00_000..30_00_000); let mut sum = 0u64; for num in 30_00_000..=40_00_000 { sum += num; } println!("Sum of numbers 1..=40_00_000: {sum}"); assert_eq!(sum, 8000002000000); }
Solution
use std::{ ops::Range, thread::{self, JoinHandle}, }; fn summation_thread(range: Range<u64>) -> JoinHandle<u64> { thread::spawn(|| { let mut sum = 0; for num in range { sum += num; } sum }) } // calculate the sum of numbers 1..=40_00_000 using four threads fn main() { let handle1 = summation_thread(1..10_00_000); let handle2 = summation_thread(10_00_000..20_00_000); let handle3 = summation_thread(20_00_000..30_00_000); let mut sum = 0u64; for num in 30_00_000..=40_00_000 { sum += num; } sum += handle1.join().unwrap(); sum += handle2.join().unwrap(); sum += handle3.join().unwrap(); println!("Sum of numbers 1..=40_00_000: {sum}"); assert_eq!(sum, 8000002000000); }
Sleeping
// When main thread terminates, all the spawned threads also get terminated. // Make the main thread sleep for half a second before terminating, to ensure that spawned thread gets enough time to complete. // Do not join the spawned thread. use std::thread; fn main() { thread::spawn(|| { println!("Count down from 10 to 1 🚀"); for num in (1..=10).rev() { println!("Count down: {num}!"); } }); println!("Count up from 1 to 10..."); for num in 1..=10 { println!("Count up: {num}"); } }
Solution
use std::thread; use std::time::Duration; fn main() { thread::spawn(|| { println!("Count down from 10 to 1 🚀"); for num in (1..=10).rev() { println!("Count down: {num}!"); } }); println!("Count up from 1 to 10..."); for num in 1..=10 { println!("Count up: {num}"); } thread::sleep(Duration::from_millis(500)); }
Moving Values into Threads
Exercises
Moving
// Fix the code to make it compile. use std::thread; fn main() { let msg = "Hello from spawned thread".to_owned(); let handle = thread::spawn(|| { println!("{msg}"); }); handle.join().unwrap(); }
Solution
use std::thread; fn main() { let msg = "Hello from spawned thread".to_owned(); let handle = thread::spawn(move || { println!("{msg}"); }); handle.join().unwrap(); }
Passing Messages between Threads
Additional Resources
Exercises
Passing message 1
// Fix the code to make it compile and produce the correct output. use std::sync::mpsc::{self, Sender}; use std::thread; fn main() { let (tx, rx) = mpsc::channel(); let nums = [12, 43, 54, 43, 53, 52, 98, 89]; sum(&nums, 3, tx); let mut res = 0; for sum in rx { res += sum; } println!("Sum of numbers: {res}"); } // calculate sum of numbers using specified number of threads fn sum(nums: &[i32], thread_count: usize, tx) { let elements_per_thread = nums.len() / thread_count; let mut start_pos; let mut partition; for i in 0..thread_count - 1 { start_pos = i * elements_per_thread; partition = Vec::from(&nums[start_pos..start_pos + elements_per_thread]); let tx_clone = tx.clone(); thread::spawn(move || { let mut sum = 0; for num in partition { sum += num; } }); } // sum the remaining elements using last thread partition = Vec::from(&nums[(thread_count - 1) * elements_per_thread..]); thread::spawn(move || { let mut sum = 0; for num in partition { sum += num; } tx.send(sum).unwrap() }); }
Solution
use std::sync::mpsc::{self, Sender}; use std::thread; fn main() { let (tx, rx) = mpsc::channel(); let nums = [12, 43, 54, 43, 53, 52, 98, 89]; sum(&nums, 3, tx); let mut res = 0; for sum in rx { res += sum; } println!("Sum of numbers: {res}"); } // calculate sum of numbers using specified number of threads fn sum(nums: &[i32], thread_count: usize, tx: Sender<i32>) { let elements_per_thread = nums.len() / thread_count; let mut start_pos; let mut partition; for i in 0..thread_count - 1 { start_pos = i * elements_per_thread; partition = Vec::from(&nums[start_pos..start_pos + elements_per_thread]); let tx_clone = tx.clone(); thread::spawn(move || { let mut sum = 0; for num in partition { sum += num; } tx_clone.send(sum).unwrap(); }); } // sum the remaining elements using last thread partition = Vec::from(&nums[(thread_count - 1) * elements_per_thread..]); thread::spawn(move || { let mut sum = 0; for num in partition { sum += num; } tx.send(sum).unwrap() }); }
Passing message 2
// Fix the code to make it compile. use std::sync::mpsc; use std::thread; fn main() { let sentences = [ "!tpircs llehs a eb ot detnaw eh esuaceB ?tsuR nrael barC eht sirreF did yhW".to_owned(), "!sgel sih fo thgie lla htiw tsuR ni edoc nac eh - reksat-itlum etamitlu eht si barC eht sirreF".to_owned() ]; let (tx, rx) = mpsc::channel(); for sentence in sentences { let tx_clone = tx.clone(); thread::spawn(|| { let reversed = sentence.chars().rev().collect::<String>(); }); } drop(tx); let printer = thread::spawn(|| { println!("Reversed sentences:"); for sentence in rx { println!("{sentence}"); } }); printer.join().unwrap(); }
Solution
use std::sync::mpsc; use std::thread; fn main() { let sentences = [ "!tpircs llehs a eb ot detnaw eh esuaceB ?tsuR nrael barC eht sirreF did yhW".to_owned(), "!sgel sih fo thgie lla htiw tsuR ni edoc nac eh - reksat-itlum etamitlu eht si barC eht sirreF".to_owned() ]; let (tx, rx) = mpsc::channel(); for sentence in sentences { let tx_clone = tx.clone(); thread::spawn(move || { let reversed = sentence.chars().rev().collect::<String>(); tx_clone.send(reversed).unwrap(); }); } drop(tx); let printer = thread::spawn(|| { println!("Reversed sentences:"); for sentence in rx { println!("{sentence}"); } }); printer.join().unwrap(); }
Sharing States between Threads
Additional Resources
Exercises
Acquiring locks
// Inorder to access a value wrapped in a mutex, it's lock has to be acquired. // Fix the code by acquiring the lock at appropriate places. use std::sync::{Arc, Mutex}; use std::thread; struct Wrapper { value: i32, } impl Wrapper { fn new() -> Self { Wrapper { value: 0 } } fn add(&mut self, to_add: i32) { self.value += to_add; } } // calculate sum of range 1..=40000 using four threads fn main() { let sum = Arc::new(Mutex::new(Wrapper::new())); let mut handles = Vec::new(); for i in 0..4 { let sum_clone = Arc::clone(&sum); let handle = thread::spawn(move || { let mut sum = 0; let start = i * 10000 + 1; for num in start..start + 10000 { sum += num; } // TODO: acquire lock and add sum to sum_clone }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } // TODO: acquire lock and print the sum value println!("Sum of range 1..=40000 : {}"); }
Solution
use std::sync::{Arc, Mutex}; use std::thread; struct Wrapper { value: i32, } impl Wrapper { fn new() -> Self { Wrapper { value: 0 } } fn add(&mut self, to_add: i32) { self.value += to_add; } } // calculate sum of range 1..=40000 using four threads fn main() { let sum = Arc::new(Mutex::new(Wrapper::new())); let mut handles = Vec::new(); for i in 0..4 { let sum_clone = Arc::clone(&sum); let handle = thread::spawn(move || { let mut sum = 0; let start = i * 10000 + 1; for num in start..start + 10000 { sum += num; } let mut lock = sum_clone.lock().unwrap(); lock.add(sum); }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } let lock = sum.lock().unwrap(); println!("Sum of range 1..=40000 : {}", lock.value); }
Atomic ref count
// Fix the code to make it compile. use std::rc::Rc; use std::sync::Mutex; use std::thread; fn is_prime(num: u32) -> bool { for i in 2..=num / 2 { if num % i == 0 { return false; } } if num <= 1 { false } else { true } } // list of all prime numbers less than 10000 using four threads fn main() { let mut primes = Rc::new(Mutex::new(Vec::new())); let thread_count = 4; let elemets_per_thread = 10000 / thread_count; let mut handles = Vec::new(); for i in 0..thread_count { let start = i * elemets_per_thread; let list_clone = Rc::clone(&primes); let handle = thread::spawn(|| { for num in start..start + elemets_per_thread { if is_prime(num) { let mut lock = list_clone.lock().unwrap(); lock.push(num); } } }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } let lock = primes.lock().unwrap(); println!("Prime numbers:"); println!("{:?}", lock); assert_eq!(lock.len(), 1229); }
Solution
use std::sync::{Mutex, Arc}; use std::thread; fn is_prime(num: u32) -> bool { for i in 2..=num / 2 { if num % i == 0 { return false; } } if num <= 1 { false } else { true } } // list of all prime numbers less than 10000 using four threads fn main() { let primes = Arc::new(Mutex::new(Vec::new())); let thread_count = 4; let elemets_per_thread = 10000 / thread_count; let mut handles = Vec::new(); for i in 0..thread_count { let start = i * elemets_per_thread; let list_clone = Arc::clone(&primes); let handle = thread::spawn(move || { for num in start..start + elemets_per_thread { if is_prime(num) { let mut lock = list_clone.lock().unwrap(); lock.push(num); } } }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } let lock = primes.lock().unwrap(); println!("Prime numbers:"); println!("{:?}", lock); assert_eq!(lock.len(), 1229); }
Send and Sync Traits
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Exercises
(No exercises yet. Feel free to contribute here!)
async/.await
Additional Resources
Exercises
Awaiting
// When executing the below code nothing is printed to console. Can you guess what is missing? #[tokio::main] async fn main() { my_function(); } async fn my_function() { println!("My first asynchronous function in rust!"); }
Solution
#[tokio::main] async fn main() { my_function().await; } async fn my_function() { println!("My first asynchronous function in rust!"); }
Creating executor
// Fix the code to make it compile. You may only add code, not remove it. use std::time::Duration; use tokio::time::sleep; struct Employee { id: u32, name: String, salary: f32, } impl Employee { fn new(id: u32, name: &str, salary: f32) -> Self { Self { id, name: name.to_string(), salary, } } } fn main() { let (id1, id2) = (2, 3); let emp1 = read_details_from_db(id1).await.unwrap(); let emp2 = read_details_from_db(id2).await.unwrap(); if emp1.salary > emp2.salary { println!( "{} earns ${} more than {}", emp1.name, emp1.salary - emp2.salary, emp2.name ); } else if emp2.salary > emp1.salary { println!( "{} earns ${} more than {}", emp2.name, emp2.salary - emp1.salary, emp1.name ); } else { println!("Both {} and {} earn same amount", emp1.name, emp2.name); } } async fn read_details_from_db(id: u32) -> Result<Employee, String> { // dummy read from database sleep(Duration::from_millis(1000)).await; let database = [ Employee::new(1, "Alice", 98000.0), Employee::new(2, "Bob", 95000.0), Employee::new(3, "Cindy", 95000.0), ]; for emp in database { if id == emp.id { return Ok(emp); } } Err("Employee record not present".into()) }
Solution
use std::time::Duration; use tokio::time::sleep; struct Employee { id: u32, name: String, salary: f32, } impl Employee { fn new(id: u32, name: &str, salary: f32) -> Self { Self { id, name: name.to_string(), salary, } } } #[tokio::main] async fn main() { let (id1, id2) = (2, 3); let emp1 = read_details_from_db(id1).await.unwrap(); let emp2 = read_details_from_db(id2).await.unwrap(); if emp1.salary > emp2.salary { println!( "{} earns ${} more than {}", emp1.name, emp1.salary - emp2.salary, emp2.name ); } else if emp2.salary > emp1.salary { println!( "{} earns ${} more than {}", emp2.name, emp2.salary - emp1.salary, emp1.name ); } else { println!("Both {} and {} earn same amount", emp1.name, emp2.name); } } async fn read_details_from_db(id: u32) -> Result<Employee, String> { // dummy read from database sleep(Duration::from_millis(1000)).await; let database = [ Employee::new(1, "Alice", 98000.0), Employee::new(2, "Bob", 95000.0), Employee::new(3, "Cindy", 95000.0), ]; for emp in database { if id == emp.id { return Ok(emp); } } Err("Employee record not present".into()) }
Tokio Tasks
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Exercises
Awaiting tasks
// Fix the code to make it compile. use std::time::Duration; use tokio::time::sleep; struct Employee { id: u32, name: String, salary: f32, } impl Employee { fn new(id: u32, name: &str, salary: f32) -> Self { Self { id, name: name.to_string(), salary, } } } #[tokio::main] async fn main() { let ids = [1, 2, 4, 5, 9, 10]; let mut handles = Vec::new(); for id in ids { let handle = tokio::spawn(async move { let res = print_details(id).await; if let Err(e) = res { println!("{e}"); } }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } } async fn print_details(id: u32) -> Result<(), String> { let emp = read_details_from_db(id).await?; println!("Id: {}, Name: {}, Salary: {}", emp.id, emp.name, emp.salary); Ok(()) } async fn read_details_from_db(id: u32) -> Result<Employee, String> { // dummy read from database sleep(Duration::from_millis(1000)).await; let database = [ Employee::new(1, "Alice", 98000.0), Employee::new(2, "Bob", 95000.0), Employee::new(3, "Cindy", 95000.0), Employee::new(4, "Daniel", 88000.0), ]; for emp in database { if id == emp.id { return Ok(emp); } } Err(format!("Employee record for id {} not present", id)) }
Solution
// Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=270cc37d8222f963cedfb137f0472584 // Fix the code to make it compile. use std::time::Duration; use tokio::time::sleep; struct Employee { id: u32, name: String, salary: f32, } impl Employee { fn new(id: u32, name: &str, salary: f32) -> Self { Self { id, name: name.to_string(), salary, } } } #[tokio::main] async fn main() { let ids = [1, 2, 4, 5, 9, 10]; let mut handles = Vec::new(); for id in ids { let handle = tokio::spawn(async move { let res = print_details(id).await; if let Err(e) = res { println!("{e}"); } }); handles.push(handle); } for handle in handles { handle.await.unwrap(); // use await here instead of join() } } async fn print_details(id: u32) -> Result<(), String> { let emp = read_details_from_db(id).await?; println!("Id: {}, Name: {}, Salary: {}", emp.id, emp.name, emp.salary); Ok(()) } async fn read_details_from_db(id: u32) -> Result<Employee, String> { // dummy read from database sleep(Duration::from_millis(1000)).await; let database = [ Employee::new(1, "Alice", 98000.0), Employee::new(2, "Bob", 95000.0), Employee::new(3, "Cindy", 95000.0), Employee::new(4, "Daniel", 88000.0), ]; for emp in database { if id == emp.id { return Ok(emp); } } Err(format!("Employee record for id {} not present", id)) }
Tokio Streams
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Exercises
(No exercises yet. Feel free to contribute here!)
Intro to Macros
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Intro to Macros
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Declarative Macros
Additional Resources
Exercises
Invocation
// Fix the code to make it compile. macro_rules! my_macro { () => { println!("Check out my macro!"); }; } fn main() { my_macro(); }
Solution
macro_rules! my_macro { () => { println!("Check out my macro!"); }; } fn main() { my_macro!(); }
Scope 1
// Everything seems correct, but the code does not compile. Maybe it has to do with the position of defining a macro. fn main() { my_macro!(); } macro_rules! my_macro { () => { println!("Check out my macro!"); }; }
Solution
macro_rules! my_macro { () => { println!("Check out my macro!"); }; } fn main() { my_macro!(); }
Scope 2
// Fix the code by bringing `my_macros` in scope (You have to mark `macros` module with something). mod macros { macro_rules! my_macro { () => { println!("Check out my macro!"); }; } } fn main() { my_macro!(); }
Solution
#[macro_use] mod macros { macro_rules! my_macro { () => { println!("Check out my macro!"); }; } } fn main() { my_macro!(); }
Multiple matchers 1
// Fix the code to make it compile. You can not remove anything. #[rustfmt::skip] macro_rules! my_macro { () => { println!("Check out my macro!"); } ($val:expr) => { println!("Look at this other macro: {}", $val); } } fn main() { my_macro!(); my_macro!(7777); }
Solution
#[rustfmt::skip] macro_rules! my_macro { () => { println!("Check out my macro!"); }; ($val:expr) => { println!("Look at this other macro: {}", $val); }; } fn main() { my_macro!(); my_macro!(7777); }
Multiple matchers 2
// We are trying to create a macro called `vec2`, which has the same functionality as the `vec` macro. // Complete the transcriber for each matcher. macro_rules! vec2 { () => {}; ($($a:expr),+ $(,)?) => {{}}; ($m:expr; $n:expr) => {{}}; } #[cfg(test)] mod tests { #[test] fn creates_empty_vector() { let first: Vec<i32> = vec![]; let second: Vec<i32> = vec2![]; assert_eq!(first, second); } #[test] fn creates_vector_from_list() { assert_eq!(vec![1, 2, 3,], vec2![1, 2, 3,]); assert_eq!(vec!['a', 'b', 'b', 'a'], vec2!['a', 'b', 'b', 'a']); } #[test] fn creates_vector_with_repeating_element() { assert_eq!(vec![5; 10], vec2![5;10]); } }
Solution
#![allow(unused)] fn main() { macro_rules! vec2 { () => { Vec::new() }; ($($a:expr),+ $(,)?) => { { let mut res = Vec::new(); $( res.push($a); )+ res } }; ($m:expr; $n:expr) => { { let mut res = Vec::new(); for _ in 0..$n { res.push($m.clone()); } res } }; } #[cfg(test)] mod tests { #[test] fn creates_empty_vector() { let first: Vec<i32> = vec![]; let second: Vec<i32> = vec2![]; assert_eq!(first, second); } #[test] fn creates_vector_from_list() { assert_eq!(vec![1, 2, 3,], vec2![1, 2, 3,]); assert_eq!(vec!['a', 'b', 'b', 'a'], vec2!['a', 'b', 'b', 'a']); } #[test] fn creates_vector_with_repeating_element() { assert_eq!(vec![5; 10], vec2![5;10]); } } }
Repetition
// Complete the definition of `sum`. macro_rules! sum { () => { { let mut sum = 0; $( sum += $a; )+ } }; } fn main() { assert_eq!(sum!(1, 2, 3), 6); assert_eq!(sum!(10u8, 20u8), 30); }
Solution
macro_rules! sum { ($($a:expr),+) => { { let mut sum = 0; $( sum += $a; )+ sum } }; } fn main() { assert_eq!(sum!(1, 2, 3), 6); assert_eq!(sum!(10u8, 20u8), 30); }
Procedural Macros
Additional Resources
Exercises
(No exercises yet. Feel free to contribute here!)
Custom Derive
Exercises
(No exercises yet. Feel free to contribute here!)
Attribute Like
Exercises
(No exercises yet. Feel free to contribute here!)
Function Like
Exercises
(No exercises yet. Feel free to contribute here!)
Intro to Unsafe Rust
Intro to Unsafe Rust
Dereferencing Raw Pointers
Exercises
Immutable raw pointers
// Fix the code to make it compile. You may not modify any statement. fn main() { let num = 123; let ptr = &num as *const i32; println!("{} stored at {:p}", *ptr, ptr); }
Solution
fn main() { let num = 123; let ptr = &num as *const i32; unsafe { println!("{} stored at {:p}", *ptr, ptr); } }
Mutable raw pointers
// Fix the code to make it compile. fn main() { // print first 10 fibonacci numbers let (mut a, mut b) = (1, 0); let mut c = 0; let ptr_a = &mut a as *const i32; let ptr_b = &mut b as *const i32; let ptr_c = &mut c as *const i32; for _ in 0..10 { *ptr_c = *ptr_a + *ptr_b; println!("{}", *ptr_c); *ptr_a = *ptr_b; *ptr_b = *ptr_c; } }
Solution
fn main() { // print first 10 fibonacci numbers let (mut a, mut b) = (1, 0); let mut c = 0; let ptr_a = &mut a as *mut i32; let ptr_b = &mut b as *mut i32; let ptr_c = &mut c as *mut i32; unsafe { for _ in 0..10 { *ptr_c = *ptr_a + *ptr_b; println!("{}", *ptr_c); *ptr_a = *ptr_b; *ptr_b = *ptr_c; } } }
Multiple pointers
// Fix the code to make it compile. macro_rules! ptr { ($type:ty, $var:ident) => { &mut $var as *mut $type }; } fn main() { let mut x = 20; let ptr1 = ptr!(i32, x); let ptr2 = ptr!(i32, x); println!("x: {x}"); *ptr1 = *ptr1 * 2; *ptr2 = *ptr2 * 2; *ptr2 = *ptr1 * 2; println!("x * 8 = {x}"); }
Solution
macro_rules! ptr { ($type:ty, $var:ident) => { &mut $var as *mut $type }; } fn main() { let mut x = 20; let ptr1 = ptr!(i32, x); let ptr2 = ptr!(i32, x); println!("x: {x}"); unsafe { *ptr1 = *ptr1 * 2; *ptr2 = *ptr2 * 2; *ptr2 = *ptr1 * 2; } println!("x * 8 = {x}"); }
Calling Unsafe Functions
Exercises
Unsafe functions
// Fix the code to make it compile. You can only add and not remove anything from the code. fn increment(a: *mut i32) { *a += 1; } fn get_val(a: *const i32) -> i32 { *a } fn main() { let mut x = 0; let ptr1 = &mut x as *mut i32; unsafe { increment(ptr1); increment(ptr1); increment(ptr1); assert_eq!(get_val(ptr1), 3); } }
Solution
unsafe fn increment(a: *mut i32) { *a += 1; } unsafe fn get_val(a: *const i32) -> i32 { *a } fn main() { let mut x = 0; let ptr1 = &mut x as *mut i32; unsafe { increment(ptr1); increment(ptr1); increment(ptr1); assert_eq!(get_val(ptr1), 3); } }
Unsafe methods
// Something is missing from the method signatures. Complete them wherever required. use std::ops::{Add, Mul, Sub}; struct VarManipulator<T>(*mut T) where T: Copy + Add<Output = T> + Mul<Output = T> + Sub<Output = T>; impl<T> VarManipulator<T> where T: Copy + Add<Output = T> + Mul<Output = T> + Sub<Output = T>, { fn new(ptr: *mut T) -> Self { Self(ptr) } fn add(&self, operand2: T) { *self.0 = *self.0 + operand2; } fn mul(&self, operand2: T) { *self.0 = *self.0 * operand2; } fn sub(&self, operand2: T) { *self.0 = *self.0 - operand2; } fn get_val(&self) -> T { *self.0 } } fn main() { let mut x = 20; let manipulator = VarManipulator::new(&mut x); unsafe { manipulator.sub(10); manipulator.mul(9); manipulator.add(10); assert_eq!(manipulator.get_val(), 100); assert_eq!(x, manipulator.get_val()); } }
Solution
use std::ops::{Add, Mul, Sub}; struct VarManipulator<T>(*mut T) where T: Copy + Add<Output = T> + Mul<Output = T> + Sub<Output = T>; impl<T> VarManipulator<T> where T: Copy + Add<Output = T> + Mul<Output = T> + Sub<Output = T>, { fn new(ptr: *mut T) -> Self { Self(ptr) } unsafe fn add(&self, operand2: T) { *self.0 = *self.0 + operand2; } unsafe fn mul(&self, operand2: T) { *self.0 = *self.0 * operand2; } unsafe fn sub(&self, operand2: T) { *self.0 = *self.0 - operand2; } unsafe fn get_val(&self) -> T { *self.0 } } fn main() { let mut x = 20; let manipulator = VarManipulator::new(&mut x); unsafe { manipulator.sub(10); manipulator.mul(9); manipulator.add(10); assert_eq!(manipulator.get_val(), 100); assert_eq!(x, manipulator.get_val()); } }
Mutable Static Variables
Exercises
Mutating
// Fix the code to make it compile. static COUNTER: u32 = 0; // return fibonacci number corresponding to specified position fn fib(num: u32) -> u32 { unsafe { COUNTER += 1; } if num <= 1 { num } else { fib(num - 1) + fib(num - 2) } } fn main() { let num = fib(5); println!("Fibonacci number at position 5: {num}"); println!("Number of function calls made to calculate: {COUNTER}"); }
Solution
static mut COUNTER: u32 = 0; // return fibonacci number corresponding to specified position fn fib(num: u32) -> u32 { unsafe { COUNTER += 1; } if num <= 1 { num } else { fib(num - 1) + fib(num - 2) } } fn main() { let num = fib(5); println!("Fibonacci number at position 5: {num}"); unsafe { println!("Number of function calls made to calculate: {COUNTER}"); } }
Implementing Unsafe Traits
Exercises
Unsafe traits
// Fix the code to make it compile. Do not modify trait definition. unsafe trait Length { fn length(&self) -> usize; } impl Length for String { fn length(&self) -> usize { self.len() } } impl Length for i32 { fn length(&self) -> usize { match self { -9..=9 => 1, _ => 1 + (self / 10).length(), } } } fn main() { let my_str = "Unsafe Traits".to_owned(); let my_num = 12323; println!("\"{my_str}\" takes {} bytes", my_str.length()); println!("{my_num} has {} digits", my_num.length()); }
Solution
unsafe trait Length { fn length(&self) -> usize; } unsafe impl Length for String { fn length(&self) -> usize { self.len() } } unsafe impl Length for i32 { fn length(&self) -> usize { match self { -9..=9 => 1, _ => 1 + (self / 10).length(), } } } fn main() { let my_str = "Unsafe Traits".to_owned(); let my_num = 12323; println!("\"{my_str}\" takes {} bytes", my_str.length()); println!("{my_num} has {} digits", my_num.length()); }
Inline Assembly
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Additional Resources
Exercises
(No exercises yet. Feel free to contribute here!)
FFI C from Rust
Exercises
(No exercises yet. Feel free to contribute here!)
FFI Rust from C
Exercises
(No exercises yet. Feel free to contribute here!)
FFI Rust from Python
Note: No official YouTube video yet, but our Rust Developer Bootcamp covers this topic!
Exercises
(No exercises yet. Feel free to contribute here!)
Rust Based Computer Science
Note: This is an exclusive of our Rust Developer Bootcamp!
Rust Based Computer Science
Note: This is an exclusive of our Rust Developer Bootcamp!
Deep Dive into Strings in Rust
Note: This is an exclusive of our Rust Developer Bootcamp!
Building Microservices in Rust
Note: This is an exclusive of our Rust Developer Bootcamp!
Systems Programming in Rust
Note: This is an exclusive of our Rust Developer Bootcamp!
CLI Project
Note: This is an exclusive of our Rust Developer Bootcamp!
CLI Project
Note: This is an exclusive of our Rust Developer Bootcamp!
API Project
Note: This is an exclusive of our Rust Developer Bootcamp!
Microservices Project
Note: This is an exclusive of our Rust Developer Bootcamp!