Rust 3 - Generics, traits, functional constructs, data structures, files
Tags: rust
Published: 2020-08-19 07:53:45

# Generics

To prevent duplication of code like this:

```rust
fn largest_i32(list: &[i32]) -> i32 {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> char {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}
```

we can simplify the code using generics:

```rust
fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}
```

## Structures

```rust
struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}
```

```rust
struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}
```

## Method

```rust
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}
```

## Enum

```rust
enum Result<T, E> {
    Ok(T),
    Err(E),
}
```

# Traits

```rust
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, 
        self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
```

## Default implementation

```rust
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}
```

## Trait as a parameter

```rust
pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
```

## Bound trait

```rust
pub fn notify<T: Summary>(item1: &T, item2: &T) { }
```

## Multiple traits

```rust
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 { }

// or
fn some_function<T, U>(t: &T, u: &U) -> i32
    where
        T: Display + Clone,
        U: Clone + Debug,
{}
```

## Return trait-compliant structure from function

```rust
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    }
}
```

# Iterators

Iterator is anything that implements an Iterator trait.

```rust
pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}
```

## Iterate vector

```rust
fn main() {
    let v1 = vec![1, 2, 3];

    let v1_iter = v1.iter();

    for val in v1_iter {
        println!("Got: {}", val);
    }

		// or 
		for val in v1 {
        println!("Got: {}", val);
    }
}
```

# Functional constructs

## Map

```rust
let a = [1, 2, 3];

let mut iter = a.iter().map(|x| 2 * x);

assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(6));
assert_eq!(iter.next(), None);
```

## Filter

```rust
let a = [1, 4, 2, 3];

let sum = a.iter()
    .cloned()
    .inspect(|x| println!("about to filter: {}", x))
    .filter(|x| x % 2 == 0)
    .inspect(|x| println!("made it through filter: {}", x))
```

## Enumerate

```rust
let a = ['a', 'b', 'c'];

let mut iter = a.iter().enumerate();

assert_eq!(iter.next(), Some((0, &'a')));
assert_eq!(iter.next(), Some((1, &'b')));
assert_eq!(iter.next(), Some((2, &'c')));
assert_eq!(iter.next(), None);
```

## Skip

```rust
let a = [1, 2, 3];

let mut iter = a.iter().skip(2);

assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None);
```

## Take

```rust
let a = [1, 2, 3];

let mut iter = a.iter().take(2);

assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), None);
```

## Sum

```rust
let a = [1, 4, 2, 3];

let sum: i32 = a.iter()
    .cloned()
    .filter(|x| x % 2 == 0)
    .sum();

println!("{}", sum);
```

## Zip

```rust
let enumerate: Vec<_> = "foo".chars().enumerate().collect();

// lazy evaluation of (0..)
let zipper: Vec<_> = (0..).zip("foo".chars()).collect();

assert_eq!((0, 'f'), enumerate[0]);
assert_eq!((0, 'f'), zipper[0]);

assert_eq!((1, 'o'), enumerate[1]);
assert_eq!((1, 'o'), zipper[1]);

assert_eq!((2, 'o'), enumerate[2]);
assert_eq!((2, 'o'), zipper[2]);
```

## Fold

```rust
let a = [1, 2, 3];

// the sum of all elements of the array
let sum = a.iter().fold(0, |acc, x| acc + x);

assert_eq!(sum, 6);
```

It is better to use `for`  than iterators for side effects.

# Data structures

## Vec

when we want to:

- push elements at the end
- use it as a stack
- have an array with dynamic size
- have an array on heap

```rust
fn main() {
    let mut v = Vec::new();

    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
}
```

## VecDeque

when we want to:

- have a vector that allows inserting elements at the beginning
- use it as a queue
- use it as a double-ended queue

```rust
use std::collections::VecDeque;

let mut buf = VecDeque::new();
buf.push_back(4);
buf.push_back(5);
buf.insert(0, 3);
if let Some(elem) = buf.get_mut(1) {
    *elem = 7;
}

assert_eq!(buf[0], 3);
assert_eq!(buf[1], 7);
assert_eq!(buf[2], 5);
```

## HashMap

when we want to:

- map keys to values
- have a cache
- use it as a dictionary

```rust
fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

		// insert a value only if it doesn't already exist
    scores.entry(String::from("Blue")).or_insert(70);
    scores.entry(String::from("Red")).or_insert(80);

    println!("Blue score: {}", scores.get("Blue").unwrap());
    println!("Yellow score: {}", scores["Yellow"]);
    println!("Red score: {}", scores["Red"]);
}
```

## HashSet

when we want to:

- store elements withou duplicates

```rust
use std::collections::HashSet;
// Type inference lets us omit an explicit type signature (which
// would be `HashSet<String>` in this example).
let mut books = HashSet::new();

// Add some books.
books.insert("A Dance With Dragons".to_string());
books.insert("To Kill a Mockingbird".to_string());
books.insert("The Odyssey".to_string());
books.insert("The Great Gatsby".to_string());

// Check for a specific one.
if !books.contains("The Winds of Winter") {
    println!("We have {} books, but The Winds of Winter ain't one.",
             books.len());
}

// Remove a book.
books.remove("The Odyssey");
```

## BTreeMap

when we want to:

- have a map ordered by keys
- get elements in range
- get smallest or largest element fast
- find keys that are greater or smaller that other

```rust
fn main() {
    use std::collections::BTreeMap;

    // type inference lets us omit an explicit type signature (which
    // would be `BTreeMap<&str, u8>` in this example).
    let mut player_stats = BTreeMap::new();

    fn random_stat_buff() -> u8 {
        // could actually return some random value here - let's just return
        // some fixed value for now
        println!("Some intensive computation.");
        42
    }

    // insert a key only if it doesn't already exist
    player_stats.entry("health").or_insert(100);
    // unnecessary intensive computation
    player_stats.entry("health").or_insert(random_stat_buff());
    // insert a key using a function that provides a new value only if it
    // doesn't already exist
    player_stats
        .entry("health")
        .or_insert_with(random_stat_buff);

    // update a key, guarding against the key possibly not being set
    let stat = player_stats.entry("attack").or_insert(100);
    *stat += random_stat_buff();
}
```

```rust
use std::collections::BTreeMap;

let mut movie_reviews = BTreeMap::new();

// review some movies.
movie_reviews.insert("Office Space",       "Deals with real issues in the workplace.");
movie_reviews.insert("Pulp Fiction",       "Masterpiece.");
movie_reviews.insert("The Godfather",      "Very enjoyable.");
movie_reviews.insert("The Blues Brothers", "I liked it a lot.");

// check for a specific one.
if !movie_reviews.contains_key("Les Misérables") {
    println!("We've got {} reviews, but Les Misérables ain't one.",
             movie_reviews.len());
}

// oops, this review has a lot of spelling mistakes, let's delete it.
movie_reviews.remove("The Blues Brothers");

// look up the values associated with some keys.
let to_find = ["Up!", "Office Space"];
for movie in &to_find {
    match movie_reviews.get(movie) {
       Some(review) => println!("{}: {}", movie, review),
       None => println!("{} is unreviewed.", movie)
    }
}

// Look up the value for a key (will panic if the key is not found).
println!("Movie review: {}", movie_reviews["Office Space"]);

// iterate over everything.
for (movie, review) in &movie_reviews {
    println!("{}: \"{}\"", movie, review);
}
```

## BinaryHeap

when we want to:

- save elements but we need to process only the largest or most important
- have a priority queue

```rust
use std::collections::BinaryHeap;

// Type inference lets us omit an explicit type signature (which
// would be `BinaryHeap<i32>` in this example).
let mut heap = BinaryHeap::new();

// We can use peek to look at the next item in the heap. In this case,
// there's no items in there yet so we get None.
assert_eq!(heap.peek(), None);

// Let's add some scores...
heap.push(1);
heap.push(5);
heap.push(2);

// Now peek shows the most important item in the heap.
assert_eq!(heap.peek(), Some(&5));

// We can check the length of a heap.
assert_eq!(heap.len(), 3);

// We can iterate over the items in the heap, although they are returned in
// a random order.
for x in &heap {
    println!("{}", x);
}

// If we instead pop these scores, they should come back in order.
assert_eq!(heap.pop(), Some(5));
assert_eq!(heap.pop(), Some(2));
assert_eq!(heap.pop(), Some(1));
assert_eq!(heap.pop(), None);

// We can clear the heap of any remaining items.
heap.clear();

// The heap should now be empty.
assert!(heap.is_empty())
```

# Files

## Read file to string

```rust
use std::env;
use std::fs;

fn main() {
    let filename = "foo.txt";
    println!("In file {}", filename);

    let contents = fs::read_to_string(filename)
        .expect("Something went wrong when reading the file");

    println!("With text:\n{}", contents);
}
```

## Working with large files

```rust
use std::fs::File;
use std::io::{self, prelude::*, BufReader};

fn main() -> io::Result<()> {
    let file = File::open("foo.txt")?;
    let reader = BufReader::new(file);

    for line in reader.lines() {
        println!("{}", line?);
    }

    Ok(())
}
```

## Write to file

```rust
use std::io::prelude::*;
use std::io::BufWriter;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let mut buffer = BufWriter::new(File::create("foo.txt")?);

    buffer.write_all(b"some bytes")?;
    buffer.write_all("Hello World".as_bytes())?;
    buffer.flush()?;
    Ok(())
}
```

## Append to file

```rust
use std::fs::OpenOptions;
use std::io::prelude::*;

fn main() {
    let mut file = OpenOptions::new()
        .write(true)  // before version 1.8, both write and append
        .append(true) // had to be true; now append(true) is enough
        .create(true)
        .open("foo.txt")
        .unwrap();

    if let Err(e) = writeln!(file, "A new line!") {
        eprintln!("Couldn't write to file: {}", e);
    }
}
```

# Exercises
## <a name="password-converter-part1"></a> Password converter
### Task
Create a program that reads password entries from first file in one format and writes them to second file in CSV format.
When there are multiple entries with the same url and login, export the last one.

### Solution
[Password converter](https://github.com/petr7555/Learning-Rust/tree/master/03/password-converter)

## <a name="adventure-game-part1"></a> Adventure game
### Task
Create an adventure game. There will be a file with scenes. Each scene contains a story and choices the user can make. Each choice moves the user to another scene. Some scenes will be marked as end scenes.

### Solution
[Adventure game](https://github.com/petr7555/Learning-Rust/tree/master/03/adventure-game)

---
Check out my learning Rust repo on GitHub!
{% github petr7555/Learning-Rust/ no-readme %}