Generics
To prevent duplication of code like this:
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:
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list {
if item > largest {
largest = item;
}
}
largest
}
Structures
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 };
}
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
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
enum Result<T, E> {
Ok(T),
Err(E),
}
Traits
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
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
Trait as a parameter
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
Bound trait
pub fn notify<T: Summary>(item1: &T, item2: &T) { }
Multiple traits
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
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.
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Iterate vector
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
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
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
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
let a = [1, 2, 3];
let mut iter = a.iter().skip(2);
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None);
Take
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
let a = [1, 4, 2, 3];
let sum: i32 = a.iter()
.cloned()
.filter(|x| x % 2 == 0)
.sum();
println!("{}", sum);
Zip
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
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
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
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
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
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
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();
}
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
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
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
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
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
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
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
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
Check out my learning Rust repo on GitHub!
Top comments (0)