diff --git a/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/Cargo.toml b/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/Cargo.toml new file mode 100644 index 0000000..56f4002 --- /dev/null +++ b/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "workout_generator" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/src/lib.rs b/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/src/lib.rs new file mode 100644 index 0000000..e3c76b4 --- /dev/null +++ b/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/src/lib.rs @@ -0,0 +1 @@ +pub mod workout; diff --git a/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/src/main.rs b/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/src/main.rs new file mode 100644 index 0000000..97804d3 --- /dev/null +++ b/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/src/main.rs @@ -0,0 +1,8 @@ +use workout_generator::workout; + +fn main() { + let simulated_user_specified_value = 10; + let simulated_random_number = 7; + + workout::generate_workout(simulated_user_specified_value, simulated_random_number); +} diff --git a/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/src/workout.rs b/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/src/workout.rs new file mode 100644 index 0000000..5a3f598 --- /dev/null +++ b/13-functional-language-features/13-1-closures/01-abstract-behavior/workout_generator/src/workout.rs @@ -0,0 +1,66 @@ +use std::{thread, time::Duration}; + +fn simulated_expensive_calculation(intensity: u32) -> u32 { + println!("calculating slowly..."); + thread::sleep(Duration::from_secs(2)); // like timeout, timer + intensity +} + +// Iteration #3: Define a closure, and store this definition in a variable so that it can be called later. +// This reintroduces the problem of the expensive calculation bein executed multiple times. +// The solution will be presented in the next crate in this series. +pub fn generate_workout(intensity: u32, random_number: u32) { + let expensive_closure = |num| simulated_expensive_calculation(num); + + if intensity < 25 { + println!("Today, do {} pushups!", expensive_closure(intensity)); + println!("Next, do {} situps!", expensive_closure(intensity)); + } else { + if random_number == 3 { + println!("Take a break today! Remeber to stay hydrated!"); + } else { + println!("Today, run for {} minutes!", expensive_closure(intensity)); + } + } +} + +// Iteration #2 - refactor by storing the result of duplicated function calls in a variable. +// Still not ideal as expensive result gets run at least once, although it's not +// needed by the `random_number == 3` `if` block. +// pub fn generate_workout(intensity: u32, random_number: u32) { +// let expensive_result = simulated_expensive_calculation(intensity); + +// if intensity < 25 { +// println!("Today, do {} pushups!", expensive_result); +// println!("Next, do {} situps!", expensive_result); +// } else { +// if random_number == 3 { +// println!("Take a break today! Remeber to stay hydrated!"); +// } else { +// println!("Today, run for {} minutes!", expensive_result); +// } +// } +// } + +// Iteration #1 - expensive calculation called multiple times instead of once. +// pub fn generate_workout(intensity: u32, random_number: u32) { +// if intensity < 25 { +// println!( +// "Today, do {} pushups!", +// simulated_expensive_calculation(intensity) +// ); +// println!( +// "Next, do {} situps!", +// simulated_expensive_calculation(intensity) +// ); +// } else { +// if random_number == 3 { +// println!("Take a break today! Remeber to stay hydrated!"); +// } else { +// println!( +// "Today, run for {} minutes!", +// simulated_expensive_calculation(intensity) +// ); +// } +// } +// } diff --git a/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/Cargo.toml b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/Cargo.toml new file mode 100644 index 0000000..56f4002 --- /dev/null +++ b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "workout_generator" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/cacher.rs b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/cacher.rs new file mode 100644 index 0000000..63cfa33 --- /dev/null +++ b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/cacher.rs @@ -0,0 +1,47 @@ +// `Cacher` struct: +// - holds a closure in `calculation` field +// - holds an optional result, `Option`, in the `value` field +pub struct Cacher +where + T: Fn(u32) -> u32, +{ + // - `calculation` field is of the generic type `T` + // - trait bounds on `T` specify that it's a closure by using the `Fn` trait + // - closure stored on `calculation` field must have on `u32` parameter and + // must return a `u32` + calculation: T, + + // The following logic is defined in `impl Cacher`: + // - before the closure is executed, `value` will be `None` + // - when code using a `Cacher` askd for the result of the closure, the `Cacher` + // will execute the closure and store the result within a `Some` variant in the + // `value` field + // - if the code asks for the result of the clousure again, instead of executing the + // closure again, the `Cacher` will return the result held in the `Some` variant + value: Option, +} + +impl Cacher +where + T: Fn(u32) -> u32, +{ + // takes a generic parameter `T`, which is defined as having the same trait bound + // as the `Cacher` struct + pub fn new(calculation: T) -> Cacher { + Cacher { + calculation, + value: None, + } + } + + pub fn value(&mut self, arg: u32) -> u32 { + match self.value { + Some(v) => v, + None => { + let v = (self.calculation)(arg); + self.value = Some(v); + v + } + } + } +} diff --git a/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/lib.rs b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/lib.rs new file mode 100644 index 0000000..f09b820 --- /dev/null +++ b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/lib.rs @@ -0,0 +1,2 @@ +pub mod cacher; +pub mod workout; diff --git a/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/main.rs b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/main.rs new file mode 100644 index 0000000..f077a05 --- /dev/null +++ b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/main.rs @@ -0,0 +1,20 @@ +use workout_generator::workout; + +fn main() { + // calculating slowly... + // Today, do 10 pushups! + // Next, do 10 situps! + let simulated_user_specified_value = 10; + let simulated_random_number = 7; + + // calculating slowly... + // Today, run for 100 minutes! + // let simulated_user_specified_value = 100; + // let simulated_random_number = 7; + + // Take a break today! Remeber to stay hydrated! + // let simulated_user_specified_value = 100; + // let simulated_random_number = 3; + + workout::generate_workout(simulated_user_specified_value, simulated_random_number); +} diff --git a/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/workout.rs b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/workout.rs new file mode 100644 index 0000000..56bd51a --- /dev/null +++ b/13-functional-language-features/13-1-closures/02-memoization-with-struct/workout_generator/src/workout.rs @@ -0,0 +1,25 @@ +use crate::cacher::Cacher; +use std::{thread, time::Duration}; + +// Iteration #: Using `Cacher` struct +pub fn generate_workout(intensity: u32, random_number: u32) { + let mut expensive_result = Cacher::new(|num| { + println!("calculating slowly..."); + thread::sleep(Duration::from_secs(2)); + num + }); + + if intensity < 25 { + println!("Today, do {} pushups!", expensive_result.value(intensity)); + println!("Next, do {} situps!", expensive_result.value(intensity)); + } else { + if random_number == 3 { + println!("Take a break today! Remeber to stay hydrated!"); + } else { + println!( + "Today, run for {} minutes!", + expensive_result.value(intensity) + ); + } + } +} diff --git a/13-functional-language-features/13-1-closures/README.md b/13-functional-language-features/13-1-closures/README.md new file mode 100644 index 0000000..28154e1 --- /dev/null +++ b/13-functional-language-features/13-1-closures/README.md @@ -0,0 +1,19 @@ +# 13.1. Closures: Anonymous functions that can capture their environment + +Closures: + +- can be saved in a variable +- can be passed as arguments to functions +- can be created in one place, and then later called to evaluate it in a different context +- can capture values from the scope in which they're defined (unlike functions) +- allow for code reuse +- allow for behavior customisation +- need to be called at least once for the compiler to infer the types of their parameters & return value, if no arguments from the outer scope are passed into the definition + +## Sample code + +[Naively call expensive function multiple times as needed](./01-abstract-behavior/workout_generator/src/workout.rs) + +[Lazy evaluation / lazily evaluate / memoization / memo / cache / caching implementation with a `Cacher` struct typed with a `Fn` trait bound that closures must match](./02-memoization-with-struct/workout_generator/src/) + +[Capturing the environment with Closures](TODO) diff --git a/13-functional-language-features/13-2-iterators/README.md b/13-functional-language-features/13-2-iterators/README.md new file mode 100644 index 0000000..802f546 --- /dev/null +++ b/13-functional-language-features/13-2-iterators/README.md @@ -0,0 +1,110 @@ +# Processing a series of items with iterators + +- iterator pattern is code that performs some task on a sequence of items in turn +- an iterator is responsible for: + - the logic of iterating over each item + - determining when a sequence is finished +- an iterator is lazy: + - it has no effect until... + - ...the code calls methods that consume the iterator to use it up + +```rust +let v1 = vec![1, 2, 3]; // vector `v1` + +// - call `iter` method defined on `Vec` +// -> creates iterator `v1_iter` over items in vector `v1` +// - at this point, the code does not do anything useful; +// - remember that an iterator is lazy +let v1_iter = v1.iter(); + +// use `v1_iter` with a `for` loop to run code on each item on every iteration +for val in v1_iter { + println!("Got: {}", val); +} +``` + +## The `Iterator` trait and the `next` method + +Iterators implement a trait named `Iterator` that is defined in the standard library: + +```rust +pub trait Iterator { + // new syntax (A): `type Item` + type Item; + + // new syntax (A): `Self::Item` + fn next(&mut self) -> Option; + + // methods with default implementations elided +} +``` + +**`new syntax (A)`** defines an [associated type](../../19-advanced-features/19-2-advanced-traits/README.md/#specifying-placeholder-types-in-trait-definitions-with-associated-types) with this trait: + +- implementing the `Iterator` trait requires an `Item` type to be defined +- the `Item` type is used in the return type of the `next` method +- the `Item` type will be the type returned from the iterator + +Implementors of the `Iterator` trait must define one method, the `next` method: + +- which returns one item of the iterator at a time wrapped in `Some`, +- and, when the iteration is over, returns `None` + +The `next` method can be called on iterators directly: + +```rust +#[test] +fn iterator_demonstration() { + let v1 = vec![1, 2, 3]; + + // Note (A) + // v1_iter must be mutable, otherwise error on `v1_iter.next()`: + // cannot borrow `v1_iter` as mutable, as it is not declared as mutable + let mut v1_iter = v1.iter(); + + // Note (B) + // `Some(&1)`, otherwise error: + // expected `&{integer}`, found integer + 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); + + // Note (C) `into_iter` takes ownership of v1 + let v1 = vec![1, 2, 3]; + let mut v1_iter = v1.into_iter(); + // Error (E1) ----------- `v1` moved due to this method call + // println!("{:?}", v1); // Error (E1) will occur + // Error (E1) ^^ value borrowed here after move + + 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); + + // Note (D) `iter_mut` + let mut v1 = vec![1, 2, 3]; + let mut v1_iter = v1.iter_mut(); + assert_eq!(v1_iter.next(), Some(&mut 1)); + assert_eq!(v1_iter.next(), Some(&mut 2)); + assert_eq!(v1_iter.next(), Some(&mut 3)); + assert_eq!(v1_iter.next(), None); +} +``` + +**Note (A)**: Iterator `v1_iter` needs to be made mutable: + +- calling the `next` method changes the internal state used by the iterator: + - the internal state is used to keep track of where the iterator is in the sequence +- calling `next`: + - consumes, or uses up, the iterator + - eats up an iterm from the iterator +- `v1_iter` needn't be made mutable when used in a `for` loop because: + - the loop takes ownership of `v1_iter` and makes it mutable behind the scenes + +**Note (B)**: Values obtained from calls to `next`: + +- are immutable references to the values in the vector `v1` +- the `iter` method in `v1.iter()` produces an iterator over immutable references: + - **Note (C)**: `into_iter` - creates an iterator that takes ownership of `v1` and returns owned values + - **Note (D)**: `iter_mut` - creates an iterator over mutable references diff --git a/13-functional-language-features/13-2-iterators/iterators/Cargo.toml b/13-functional-language-features/13-2-iterators/iterators/Cargo.toml new file mode 100644 index 0000000..069541a --- /dev/null +++ b/13-functional-language-features/13-2-iterators/iterators/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "iterators" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/13-functional-language-features/13-2-iterators/iterators/src/main.rs b/13-functional-language-features/13-2-iterators/iterators/src/main.rs new file mode 100644 index 0000000..b46182b --- /dev/null +++ b/13-functional-language-features/13-2-iterators/iterators/src/main.rs @@ -0,0 +1,23 @@ +fn main() { + let v = vec![1, 2, 3]; + let mut v_iter = v.iter(); + assert_eq!(v_iter.next(), Some(&1)); + assert_eq!(v_iter.next(), Some(&2)); + assert_eq!(v_iter.next(), Some(&3)); + assert_eq!(v_iter.next(), None); + + let v1 = vec![1, 2, 3]; + let mut v_iter = v1.into_iter(); + println!("{:?}", v1); + assert_eq!(v_iter.next(), Some(1)); + assert_eq!(v_iter.next(), Some(2)); + assert_eq!(v_iter.next(), Some(3)); + assert_eq!(v_iter.next(), None); + + let mut v = vec![1, 2, 3]; + let mut v_iter = v.iter_mut(); + assert_eq!(v_iter.next(), Some(&mut 1)); + assert_eq!(v_iter.next(), Some(&mut 2)); + assert_eq!(v_iter.next(), Some(&mut 3)); + assert_eq!(v_iter.next(), None); +} diff --git a/13-functional-language-features/README.md b/13-functional-language-features/README.md new file mode 100644 index 0000000..f719f39 --- /dev/null +++ b/13-functional-language-features/README.md @@ -0,0 +1 @@ +# 13. Functional language features: iterators and closures diff --git a/19-advanced-features/19-2-advanced-traits/README.md b/19-advanced-features/19-2-advanced-traits/README.md new file mode 100644 index 0000000..1cee064 --- /dev/null +++ b/19-advanced-features/19-2-advanced-traits/README.md @@ -0,0 +1,7 @@ +# Advanced traits + +## Specifying placeholder types in trait definitions with associated types + +Referenced by: + +- [The `Iterator` trait and the `next` method](../../13-functional-language-features/13-2-iterators/README.md/#the-iterator-trait-and-the-next-method) diff --git a/19-advanced-features/README.md b/19-advanced-features/README.md new file mode 100644 index 0000000..7c935bd --- /dev/null +++ b/19-advanced-features/README.md @@ -0,0 +1 @@ +# Advanced features