Rust Programming Complete Guide Part 2

Rust Programming Complete Guide Part 2

Writing Automated Tests

Writing automated tests to ensure your code behaves as expected.

How to Write Tests:

    • Tests are functions annotated with the #[test] attribute.
    • Example:
      rust #[cfg(test)] mod tests { #[test] fn it_works() { let result = 2 + 2; assert_eq!(result, 4); } }

    The Anatomy of a Test Function:

      • Test functions are written inside a module annotated with #[cfg(test)] to ensure they are only compiled when running tests.
      • The #[test] attribute marks a function as a test.

      Checking Results with the assert! Macro:

        • The assert! macro ensures a condition is true. If it’s not, the test will fail.
        • Example: #[test] fn larger_can_hold_smaller() { let larger = Rectangle { width: 8, height: 7 }; let smaller = Rectangle { width: 5, height: 1 }; assert!(larger.can_hold(&smaller)); }

        Testing Equality with assert_eq! and assert_ne! Macros:

          • assert_eq! checks if two values are equal.
          • assert_ne! checks if two values are not equal.
          • Example:
            rust #[test] fn it_adds_two() { assert_eq!(add_two(2), 4); }

          Adding Custom Failure Messages:

            • Custom messages can be added to assertions for more informative test results.
            • Example:
              rust #[test] fn greeting_contains_name() { let result = greeting("Carol"); assert!(result.contains("Carol"), "Greeting did not contain name, value was `{}`", result); }

            Checking for Panics with should_panic:

              • Use the #[should_panic] attribute to indicate that a test should panic.
              • Example:
                rust #[test] #[should_panic(expected = "Guess value must be less than or equal to 100")] fn greater_than_100() { Guess::new(200); }

              Using Result<T, E> in Tests:

                • Test functions can return Result<T, E> for more flexibility.
                • Example:
                  rust #[test] fn it_works() -> Result<(), String> { if 2 + 2 == 4 { Ok(()) } else { Err(String::from("two plus two does not equal four")) } }

                Controlling How Tests Are Run:

                  • Tests can be run in parallel or consecutively.
                  • Example of running tests in parallel:
                    sh $ cargo test

                  Running a Subset of Tests by Name:

                    • You can run a specific test or a set of tests matching a name pattern.
                    • Example:
                      sh $ cargo test larger_can_hold_smaller

                    Ignoring Some Tests Unless Specifically Requested:

                    • Tests can be ignored by default and only run when explicitly requested using the #[ignore] attribute.
                    • Example:
                    #[test] #[ignore] fn expensive_test() { // code that takes a long time to run }

                    Test Organization:

                    • Tests can be organized into modules and submodules.
                    • Integration tests are placed in the tests directory and are separate from the unit tests in the main library or binary.
                    • Example of a basic integration test setup:
                    // In src/lib.rs pub fn add_two(a: i32) -> i32 { a + 2 } // In tests/integration_test.rs extern crate my_crate; #[test] fn it_adds_two() { assert_eq!(my_crate::add_two(2), 4); }

                      Functional Language Features in Rust

                      Functional programming features in Rust, such as closures, iterators, and how they contribute to writing concise and expressive code. Here are the key points:

                      Closures: Anonymous Functions that Capture Their Environment:

                        • Closures are similar to functions but can capture variables from their enclosing scope.
                        • Example of a basic closure:
                          rust let add_one = |x: i32| -> i32 { x + 1 }; println!("{}", add_one(5));
                        • Closures can capture values from their environment in three ways: by borrowing, by mutable borrowing, and by taking ownership.

                        Capturing the Environment with Closures:

                          • Closures automatically capture values from their environment.
                          • Example:
                            rust let x = 4; let equal_to_x = |z| z == x; let y = 4; assert!(equal_to_x(y));

                          Type Inference and Annotation:

                            • Rust can infer the types of parameters and return values for closures in most cases, allowing for concise syntax.
                            • Example:
                              rust let add_one = |x| x + 1;

                            Storing Closures:

                              • Closures can be stored in variables or passed to functions.
                              • Example:
                                rust let example_closure = |x| x; let s = example_closure(String::from("hello"));

                              Using Closures That Capture Their Environment:

                                • Example with capturing the environment:
                                  rust let x = vec![1, 2, 3]; let equal_to_x = move |z| z == x; // x is moved into the closure

                                Iterators: Processing a Series of Elements:

                                  • Iterators allow you to perform operations on a sequence of elements.
                                  • Example of creating an iterator:
                                    rust let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { println!("{}", val); }

                                  The Iterator Trait and the next Method:

                                    • The Iterator trait defines the next method, which returns an option containing the next element.
                                    • Example:
                                      rust let mut v1_iter = v1.iter(); assert_eq!(v1_iter.next(), Some(&1)); assert_eq!(v1_iter.next(), Some(&2)); assert_eq!(v1_iter.next(), Some(&3)); assert_eq!(v1_iter.next(), None);

                                    Methods that Produce Other Iterators:

                                      • Iterators have methods like map, filter, and collect to create other iterators.
                                      • Example of using map:
                                        rust let v1: Vec<i32> = vec![1, 2, 3]; let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); assert_eq!(v2, vec![2, 3, 4]);

                                      Using Closures that Capture Their Environment with Iterators:

                                        • Combining closures and iterators can lead to powerful and concise code.
                                        • Example of using filter and map:
                                          rust let v1: Vec<i32> = vec![1, 2, 3, 4, 5]; let v2: Vec<_> = v1.into_iter().filter(|&x| x % 2 == 0).map(|x| x * 2).collect(); assert_eq!(v2, vec![4, 8]);

                                        Creating Custom Iterators with the Iterator Trait:

                                        • Implement the Iterator trait to create custom iterators.
                                        • Example:
                                        struct Counter { count: u32, } impl Counter { fn new() -> Counter { Counter { count: 0 } } } impl Iterator for Counter { type Item = u32; fn next(&amp;mut self) -&gt; Option&lt;Self::Item&gt; { self.count += 1; if self.count &lt; 6 { Some(self.count) } else { None } } } let mut counter = Counter::new(); assert_eq!(counter.next(), Some(1)); assert_eq!(counter.next(), Some(2));

                                        Performance of Iterators:

                                        • Rust’s iterators are zero-cost abstractions, meaning they compile down to the same code as if you wrote the loop by hand.
                                        • This ensures that using iterators does not incur a performance penalty.

                                          Functional Language Features in Rust

                                          Explores functional programming features in Rust, such as closures, iterators, and how they contribute to writing concise and expressive code.

                                          Closures: Anonymous Functions that Capture Their Environment:

                                            • Closures are similar to functions but can capture variables from their enclosing scope.
                                            • Example of a basic closure:
                                              rust let add_one = |x: i32| -> i32 { x + 1 }; println!("{}", add_one(5));
                                            • Closures can capture values from their environment in three ways: by borrowing, by mutable borrowing, and by taking ownership.

                                            Capturing the Environment with Closures:

                                              • Closures automatically capture values from their environment.
                                              • Example:
                                                rust let x = 4; let equal_to_x = |z| z == x; let y = 4; assert!(equal_to_x(y));

                                              Type Inference and Annotation:

                                                • Rust can infer the types of parameters and return values for closures in most cases, allowing for concise syntax.
                                                • Example:
                                                  rust let add_one = |x| x + 1;

                                                Storing Closures:

                                                  • Closures can be stored in variables or passed to functions.
                                                  • Example:
                                                    rust let example_closure = |x| x; let s = example_closure(String::from("hello"));

                                                  Using Closures That Capture Their Environment:

                                                    • Example with capturing the environment:
                                                      rust let x = vec![1, 2, 3]; let equal_to_x = move |z| z == x; // x is moved into the closure

                                                    Iterators: Processing a Series of Elements:

                                                      • Iterators allow you to perform operations on a sequence of elements.
                                                      • Example of creating an iterator:
                                                        rust let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { println!("{}", val); }

                                                      The Iterator Trait and the next Method:

                                                        • The Iterator trait defines the next method, which returns an option containing the next element.
                                                        • Example:
                                                          rust let mut v1_iter = v1.iter(); assert_eq!(v1_iter.next(), Some(&1)); assert_eq!(v1_iter.next(), Some(&2)); assert_eq!(v1_iter.next(), Some(&3)); assert_eq!(v1_iter.next(), None);

                                                        Methods that Produce Other Iterators:

                                                          • Iterators have methods like map, filter, and collect to create other iterators.
                                                          • Example of using map:
                                                            rust let v1: Vec<i32> = vec![1, 2, 3]; let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); assert_eq!(v2, vec![2, 3, 4]);

                                                          Using Closures that Capture Their Environment with Iterators:

                                                            • Combining closures and iterators can lead to powerful and concise code.
                                                            • Example of using filter and map:
                                                              rust let v1: Vec<i32> = vec![1, 2, 3, 4, 5]; let v2: Vec<_> = v1.into_iter().filter(|&x| x % 2 == 0).map(|x| x * 2).collect(); assert_eq!(v2, vec![4, 8]);

                                                            Creating Custom Iterators with the Iterator Trait:

                                                            • Implement the Iterator trait to create custom iterators.
                                                            • Example:
                                                            struct Counter { count: u32, } impl Counter { fn new() -> Counter { Counter { count: 0 } } } impl Iterator for Counter { type Item = u32; fn next(&amp;mut self) -&gt; Option&lt;Self::Item&gt; { self.count += 1; if self.count &lt; 6 { Some(self.count) } else { None } } } let mut counter = Counter::new(); assert_eq!(counter.next(), Some(1)); assert_eq!(counter.next(), Some(2));

                                                            Performance of Iterators:

                                                            • Rust’s iterators are zero-cost abstractions, meaning they compile down to the same code as if you wrote the loop by hand.
                                                            • This ensures that using iterators does not incur a performance penalty.

                                                              Summary of Chapter 15: Smart Pointers

                                                              Chapter 15 of “The Rust Programming Language” covers smart pointers, which are data structures that act like a pointer but have additional metadata and capabilities. Here are the key points:

                                                              1. Smart Pointers Overview:
                                                              • Smart pointers provide more functionality than regular references, such as automatic memory management and additional operations.
                                                              • Common smart pointers in Rust include Box<T>, Rc<T>, and RefCell<T>.
                                                              1. Using Box<T> to Point to Data on the Heap:
                                                              • Box<T> allows you to store data on the heap instead of the stack.
                                                              • Example:
                                                                rust let b = Box::new(5); println!("b = {}", b);
                                                              1. Enabling Recursive Types with Box<T>:
                                                              • Box<T> is used to create recursive types where the size of the type cannot be known at compile time.
                                                              • Example of a recursive type:
                                                                rust enum List { Cons(i32, Box<List>), Nil, }
                                                              1. Treating Smart Pointers Like Regular References with the Deref Trait:
                                                              • The Deref trait allows instances of smart pointers to behave like references.
                                                              • Example: use std::ops::Deref; struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } impl<T> Deref for MyBox<T> { type Target = T; fn deref(&amp;self) -&gt; &amp;T { &amp;self.0 } }
                                                              1. Running Code on Cleanup with the Drop Trait:
                                                              • The Drop trait allows you to specify code that runs when a smart pointer goes out of scope.
                                                              • Example: struct CustomSmartPointer { data: String, } impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); } } let c = CustomSmartPointer { data: String::from("my stuff") };
                                                              1. Reference Counting with Rc<T>:
                                                              • Rc<T> is a reference-counting smart pointer used when multiple parts of the program need to own the same data.
                                                              • Example: use std::rc::Rc; let a = Rc::new(5); let b = Rc::clone(&a); println!("count after creating b = {}", Rc::strong_count(&a));
                                                              1. RefCell and the Interior Mutability Pattern:
                                                              • RefCell<T> allows for mutable borrowing at runtime, even when the RefCell itself is immutable.
                                                              • Example: use std::cell::RefCell; let x = RefCell::new(5); *x.borrow_mut() += 1; println!("{}", x.borrow());
                                                              1. Combining Rc<T> and RefCell<T> to Have Multiple Owners of Mutable Data:
                                                              • Combining Rc<T> and RefCell<T> allows multiple owners to mutate the same data.
                                                              • Example: use std::rc::Rc; use std::cell::RefCell; let value = Rc::new(RefCell::new(5)); let a = Rc::clone(&value); let b = Rc::clone(&value); *a.borrow_mut() += 1; println!("{}", b.borrow());
                                                              1. Creating a Tree Data Structure:
                                                              • Example of a tree data structure using Rc<T> and RefCell<T>: use std::rc::Rc; use std::cell::RefCell; #[derive(Debug)] enum List { Cons(i32, RefCell<Rc<List>>), Nil, } impl List { fn tail(&self) -> Option<&RefCell<Rc<List>>> { match self { List::Cons(_, item) => Some(item), List::Nil => None, } } } let a = Rc::new(List::Cons(5, RefCell::new(Rc::new(List::Nil)))); let b = Rc::new(List::Cons(10, RefCell::new(Rc::clone(&a)))); if let Some(link) = a.tail() { *link.borrow_mut() = Rc::clone(&b); }

                                                              Smart Pointers

                                                              Smart Pointers Overview:

                                                                • Smart pointers provide more functionality than regular references, such as automatic memory management and additional operations.
                                                                • Common smart pointers in Rust include Box<T>, Rc<T>, and RefCell<T>.

                                                                Using Box<T> to Point to Data on the Heap:

                                                                  • Box<T> allows you to store data on the heap instead of the stack.
                                                                  • Example:
                                                                    rust let b = Box::new(5); println!("b = {}", b);

                                                                  Enabling Recursive Types with Box<T>:

                                                                    • Box<T> is used to create recursive types where the size of the type cannot be known at compile time.
                                                                    • Example of a recursive type:
                                                                      rust enum List { Cons(i32, Box<List>), Nil, }

                                                                    Treating Smart Pointers Like Regular References with the Deref Trait:

                                                                      • The Deref trait allows instances of smart pointers to behave like references.
                                                                      • Example: use std::ops::Deref; struct MyBox<T>(T); impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } impl<T> Deref for MyBox<T> { type Target = T; fn deref(&amp;self) -&gt; &amp;T { &amp;self.0 } }

                                                                      Running Code on Cleanup with the Drop Trait:

                                                                        • The Drop trait allows you to specify code that runs when a smart pointer goes out of scope.
                                                                        • Example: struct CustomSmartPointer { data: String, } impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); } } let c = CustomSmartPointer { data: String::from("my stuff") };

                                                                        Reference Counting with Rc<T>:

                                                                          • Rc<T> is a reference-counting smart pointer used when multiple parts of the program need to own the same data.
                                                                          • Example: use std::rc::Rc; let a = Rc::new(5); let b = Rc::clone(&a); println!("count after creating b = {}", Rc::strong_count(&a));

                                                                          RefCell and the Interior Mutability Pattern:

                                                                            • RefCell<T> allows for mutable borrowing at runtime, even when the RefCell itself is immutable.
                                                                            • Example: use std::cell::RefCell; let x = RefCell::new(5); *x.borrow_mut() += 1; println!("{}", x.borrow());

                                                                            Combining Rc<T> and RefCell<T> to Have Multiple Owners of Mutable Data:

                                                                              • Combining Rc<T> and RefCell<T> allows multiple owners to mutate the same data.
                                                                              • Example: use std::rc::Rc; use std::cell::RefCell; let value = Rc::new(RefCell::new(5)); let a = Rc::clone(&value); let b = Rc::clone(&value); *a.borrow_mut() += 1; println!("{}", b.borrow());

                                                                              Creating a Tree Data Structure:

                                                                                • Example of a tree data structure using Rc<T> and RefCell<T>: use std::rc::Rc; use std::cell::RefCell; #[derive(Debug)] enum List { Cons(i32, RefCell<Rc<List>>), Nil, } impl List { fn tail(&self) -> Option<&RefCell<Rc<List>>> { match self { List::Cons(_, item) => Some(item), List::Nil => None, } } } let a = Rc::new(List::Cons(5, RefCell::new(Rc::new(List::Nil)))); let b = Rc::new(List::Cons(10, RefCell::new(Rc::clone(&a)))); if let Some(link) = a.tail() { *link.borrow_mut() = Rc::clone(&b); }

                                                                                Fearless Concurrency

                                                                                Using Threads to Run Code Simultaneously:

                                                                                  • Threads allow multiple parts of a program to execute simultaneously.
                                                                                  • Example of creating a thread: use std::thread; use std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap(); }

                                                                                  Using move Closures with Threads:

                                                                                    • The move keyword allows closures to take ownership of values from the environment.
                                                                                    • Example: use std::thread; let v = vec![1, 2, 3]; let handle = thread::spawn(move || { println!("Here's a vector: {:?}", v); }); handle.join().unwrap();

                                                                                    Message Passing to Transfer Data Between Threads:

                                                                                      • Channels provide a way to transfer data safely between threads.
                                                                                      • Example of creating and using a channel: use std::sync::mpsc; use std::thread; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); println!("Got: {}", received); }

                                                                                      Channels and Ownership:

                                                                                        • Sending values through channels transfers ownership to the receiver.
                                                                                        • Example of sending multiple messages: use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let vals = vec![ String::from("hi"), String::from("from"), String::from("the"), String::from("thread"), ]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_millis(1)); } }); for received in rx { println!("Got: {}", received); } }

                                                                                        Shared-State Concurrency:

                                                                                          • Rust ensures safe shared-state concurrency through the ownership system.
                                                                                          • Using Mutex<T> to manage shared state: use std::sync::{Arc, Mutex}; use std::thread; fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&amp;counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap()); }

                                                                                          Atomic Reference Counting with Arc<T>:

                                                                                            • Arc<T> (Atomic Reference Counted) is used to safely share ownership between threads.
                                                                                            • Example: use std::sync::Arc; use std::thread; let numbers = Arc::new(vec![1, 2, 3]); for _ in 0..10 { let numbers = Arc::clone(&numbers); thread::spawn(move || { println!("{:?}", numbers); }); }

                                                                                            Combining Arc<T> and Mutex<T>:

                                                                                              • Combining Arc<T> and Mutex<T> allows multiple threads to mutate shared data safely.
                                                                                              • Example: use std::sync::{Arc, Mutex}; use std::thread; let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap());

                                                                                              Object-Oriented Programming Features

                                                                                              Characteristics of Object-Oriented Languages:

                                                                                                • Object-oriented programming is typically defined by three characteristics: encapsulation, inheritance, and polymorphism.
                                                                                                • Rust provides encapsulation and polymorphism but does not have inheritance in the traditional sense.

                                                                                                Encapsulation with Structs and impl:

                                                                                                  • Encapsulation involves grouping related data and behavior together, which can be achieved using structs and impl blocks.
                                                                                                  • Example: pub struct AveragedCollection { list: Vec<i32>, average: f64, } impl AveragedCollection { pub fn add(&mut self, value: i32) { self.list.push(value); self.update_average(); } pub fn remove(&amp;mut self) -&gt; Option&lt;i32&gt; { let result = self.list.pop(); match result { Some(value) =&gt; { self.update_average(); Some(value) } None =&gt; None, } } pub fn average(&amp;self) -&gt; f64 { self.average } fn update_average(&amp;mut self) { let total: i32 = self.list.iter().sum(); self.average = total as f64 / self.list.len() as f64; } }

                                                                                                  Inheritance as a Type System and Code Sharing Mechanism:

                                                                                                    • Rust does not have inheritance, but it achieves code reuse through composition and traits.
                                                                                                    • Traits are used to define shared behavior.

                                                                                                    Polymorphism with Traits:

                                                                                                      • Polymorphism is the ability to use different types interchangeably.
                                                                                                      • Rust achieves polymorphism through trait objects.
                                                                                                      • Example: pub trait Draw { fn draw(&self); } pub struct Screen { pub components: Vec<Box<dyn Draw>>, } impl Screen { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } } pub struct Button { pub width: u32, pub height: u32, pub label: String, } impl Draw for Button { fn draw(&self) { // code to draw a button } }

                                                                                                      Trait Objects for Dynamic Dispatch:

                                                                                                        • Trait objects allow for dynamic dispatch, enabling polymorphic behavior at runtime.
                                                                                                        • Example:
                                                                                                          rust let screen = Screen { components: vec![ Box::new(Button { width: 50, height: 10, label: String::from("OK"), }), ], };

                                                                                                        Object Safety:

                                                                                                          • For a trait to be used as a trait object, it must be object-safe.
                                                                                                          • A trait is object-safe if all the methods defined in the trait have the following properties:
                                                                                                            • The return type isn’t Self.
                                                                                                            • There are no generic type parameters.

                                                                                                          Designing with Traits Instead of Inheritance:

                                                                                                            • Instead of inheritance, Rust encourages composition and using traits to define shared behavior.
                                                                                                            • Example: pub trait Messenger { fn send(&self, msg: &str); } pub struct LimitTracker<'a, T: 'a + Messenger> { messenger: &'a T, value: usize, max: usize, } impl<'a, T> LimitTracker<'a, T> where T: Messenger, { pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> { LimitTracker { messenger, value: 0, max, } } pub fn set_value(&amp;mut self, value: usize) { self.value = value; let percentage_of_max = self.value as f64 / self.max as f64; if percentage_of_max &gt;= 1.0 { self.messenger.send("Error: You are over your quota!"); } else if percentage_of_max &gt;= 0.9 { self.messenger.send("Urgent warning: You've used up over 90% of your quota!"); } else if percentage_of_max &gt;= 0.75 { self.messenger.send("Warning: You've used up over 75% of your quota!"); } } }

                                                                                                            Patterns and Matching

                                                                                                            All the Places Patterns Can Be Used:

                                                                                                              • Patterns are used in match arms, if let expressions, while let expressions, for loops, let statements, and function parameters.

                                                                                                              Refutability: Whether a Pattern Might Fail to Match:

                                                                                                                • Patterns are either refutable or irrefutable.
                                                                                                                • Refutable patterns can fail to match (e.g., Some(x)), while irrefutable patterns cannot fail to match (e.g., x).

                                                                                                                Pattern Syntax:

                                                                                                                  • Patterns can match literals, variables, wildcards, and complex structures.
                                                                                                                  • Example of matching literals and variables:
                                                                                                                    rust let x = 1; match x { 1 => println!("One"), _ => println!("Not one"), }

                                                                                                                  Destructuring to Break Apart Values:

                                                                                                                    • Destructuring structs, enums, and tuples to extract inner values.
                                                                                                                    • Example of destructuring a tuple:
                                                                                                                      rust let (x, y, z) = (1, 2, 3); println!("x: {}, y: {}, z: {}", x, y, z);

                                                                                                                    Destructuring Structs:

                                                                                                                      • Example:
                                                                                                                        rust struct Point { x: i32, y: i32, } let p = Point { x: 0, y: 7 }; let Point { x, y } = p; println!("x: {}, y: {}", x, y);

                                                                                                                      Destructuring Enums:

                                                                                                                        • Example:
                                                                                                                          rust enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } let msg = Message::ChangeColor(0, 160, 255); match msg { Message::Quit => println!("Quit"), Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y), Message::Write(text) => println!("Text: {}", text), Message::ChangeColor(r, g, b) => println!("Change color to red: {}, green: {}, blue: {}", r, g, b), }

                                                                                                                        Destructuring Nested Structures:

                                                                                                                          • Example:
                                                                                                                            rust let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });

                                                                                                                          Ignoring Values in a Pattern:

                                                                                                                            • Using _ to ignore values:
                                                                                                                              rust fn foo(_: i32, y: i32) { println!("This code only uses the y parameter: {}", y); }

                                                                                                                            Ignoring Parts of a Value with a Nested _:

                                                                                                                              • Example:
                                                                                                                                rust let mut setting_value = Some(5); let new_setting_value = Some(10); match (setting_value, new_setting_value) { (Some(_), Some(_)) => { println!("Can't overwrite an existing customized value"); } _ => { setting_value = new_setting_value; } } println!("setting is {:?}", setting_value);

                                                                                                                              Ignoring an Unused Variable by Starting Its Name with _:

                                                                                                                              • Example:
                                                                                                                              let _x = 5; let y = 10; println!("y: {}", y);

                                                                                                                              Using _ to Ignore the Remaining Parts of a Value:

                                                                                                                              • Example:
                                                                                                                              let s = Some(String::from("Hello!")); if let Some(_) = s { println!("Found a string"); } println!("{:?}", s);

                                                                                                                              Ignoring Values with ..:

                                                                                                                              • Using .. to ignore parts of a value:
                                                                                                                              struct Point { x: i32, y: i32, z: i32, } let origin = Point { x: 0, y: 0, z: 0 }; match origin { Point { x, .. } => println!("x is {}", x), }

                                                                                                                              Extra Conditionals with Match Guards:

                                                                                                                              • Adding extra conditions in match arms:
                                                                                                                              let num = Some(4); match num { Some(x) if x < 5 => println!("less than five: {}", x), Some(x) => println!("{}", x), None => (), }

                                                                                                                              @ Bindings:

                                                                                                                              • Using @ to bind a value to a variable while also testing it:
                                                                                                                                rust enum Message { Hello { id: i32 }, } let msg = Message::Hello { id: 5 }; match msg { Message::Hello { id: id_variable @ 3..=7 } => println!("Found an id in range: {}", id_variable), Message::Hello { id: 10..=12 } => println!("Found an id in another range"), Message::Hello { id } => println!("Found some other id: {}", id), }

                                                                                                                                Advanced Features

                                                                                                                                Unsafe Rust:

                                                                                                                                  • Unsafe Rust allows you to bypass some of Rust’s safety guarantees.
                                                                                                                                  • Four main actions you can perform in unsafe code:
                                                                                                                                    1. Dereference a raw pointer.
                                                                                                                                    2. Call an unsafe function or method.
                                                                                                                                    3. Access or modify a mutable static variable.
                                                                                                                                    4. Implement an unsafe trait.
                                                                                                                                  • Example: let mut num = 5; let r1 = &num as *const i32; let r2 = &mut num as *mut i32; unsafe { println!("r1 is: {}", *r1); println!("r2 is: {}", *r2); }

                                                                                                                                  Advanced Traits:

                                                                                                                                    • Associated types, default type parameters, and fully qualified syntax.
                                                                                                                                    • Example of associated types:
                                                                                                                                      rust pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }
                                                                                                                                    • Using default type parameters for traits:
                                                                                                                                      rust trait Add<RHS = Self> { type Output; fn add(self, rhs: RHS) -> Self::Output; }

                                                                                                                                    Advanced Types:

                                                                                                                                      • Newtype pattern and type aliases.
                                                                                                                                      • Example of newtype pattern: struct Kilometers(i32); let x = Kilometers(10); let y = Kilometers(20);
                                                                                                                                      • Example of type alias:
                                                                                                                                        rust type Thunk = Box<dyn Fn() + Send + 'static>; let f: Thunk = Box::new(|| println!("hi"));

                                                                                                                                      Advanced Functions and Closures:

                                                                                                                                        • Function pointers and returning closures.
                                                                                                                                        • Example of function pointers: fn add_one(x: i32) -> i32 { x + 1 } let f: fn(i32) -> i32 = add_one; println!("The result is: {}", f(5));
                                                                                                                                        • Example of returning closures:
                                                                                                                                          rust fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) }

                                                                                                                                        Macros:

                                                                                                                                          • Macros for metaprogramming, such as macro_rules! and procedural macros.
                                                                                                                                          • Example of a declarative macro: macro_rules! vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; } let v = vec![1, 2, 3];
                                                                                                                                          • Procedural macros for custom derive, attribute-like macros, and function-like macros.

                                                                                                                                          Back To Top
                                                                                                                                          Theme Mode