Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod workout;
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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)
// );
// }
// }
// }
Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// `Cacher` struct:
// - holds a closure in `calculation` field
// - holds an optional result, `Option<u32>`, in the `value` field
pub struct Cacher<T>
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<T> Cacher<T>`:
// - 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<u32>,
}

impl<T> Cacher<T>
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<T> {
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
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod cacher;
pub mod workout;
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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)
);
}
}
}
19 changes: 19 additions & 0 deletions 13-functional-language-features/13-1-closures/README.md
Original file line number Diff line number Diff line change
@@ -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)
110 changes: 110 additions & 0 deletions 13-functional-language-features/13-2-iterators/README.md
Original file line number Diff line number Diff line change
@@ -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<T>`
// -> 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<Self::Item>;

// 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
Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 1 addition & 0 deletions 13-functional-language-features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# 13. Functional language features: iterators and closures
7 changes: 7 additions & 0 deletions 19-advanced-features/19-2-advanced-traits/README.md
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions 19-advanced-features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Advanced features