09. Конвертиране на типове, итератори

09. Конвертиране на типове, итератори

09. Конвертиране на типове, итератори

8 ноември 2017

Административни неща

Второ домашно!

Въпрос

Какви lifetimes трябва да се добавят, за да се компилира?

fn substring_starting_from(text: &str, query: &str) -> &str {
    match text.find(query) {
        Some(index) => &text[index..],
        None => "",
    }
}

fn main() {
    let result = substring_starting_from("Едно ферари с цвят #FF0000", "цвят");
    println!("{}", result);

    let text = String::from("Едно ферари с цвят #FF0000");
    let result = substring_starting_from(&text, &String::from("цвят"));
    println!("{}", result);
}

Отговор

Първия аргумент, иначе временния низ като втори гърми

fn substring_starting_from<'a>(text: &'a str, query: &str) -> &'a str {
    match text.find(query) {
        Some(index) => &text[index..],
        None => "",
    }
}

fn main() {
    let result = substring_starting_from("Едно ферари с цвят #FF0000", "цвят");
    println!("{}", result);

    let text = String::from("Едно ферари с цвят #FF0000");
    let result = substring_starting_from(&text, &String::from("цвят"));
    println!("{}", result);
}

Въпрос

А ако имаме само статични низове?

fn substring_starting_from(text: &str, query: &str) -> &str {
    match text.find(query) {
        Some(index) => &text[index..],
        None => "",
    }
}

fn main() {
    let result = substring_starting_from("Едно ферари с цвят #FF0000", "цвят");
    println!("{}", result);
}

Отговор

Доста вариации работят (в конкретния случай)

fn substring_starting_from<'a>(text: &'a str, query: &str) -> &'a str {
fn substring_starting_from<'a>(text: &'a str, query: &'a str) -> &'a str {

fn substring_starting_from(text: &'static str, query: &str) -> &'static str {
fn substring_starting_from(text: &'static str, query: &'static str) -> &'static str {

// компилационна грешка:
// fn substring_starting_from(text: &str, query: &'static str) -> &'static str {

Въпрос

А ако не match-ва низа и върнем ""?

fn substring_starting_from(text: &str, query: &str) -> &str {
    match text.find(query) {
        Some(index) => &text[index..],
        None => "",
    }
}

fn main() {
    let result = substring_starting_from("Едно ферари с цвят #FF0000", "ламборгини");
    println!("{}", result);
}

Отговор

Същите. Компилатора гледа *всички* варианти на match-а, понеже няма как да знае коя ще е истинската at runtime.

fn substring_starting_from<'a>(text: &'a str, query: &str) -> &'a str {
fn substring_starting_from<'a>(text: &'a str, query: &'a str) -> &'a str {

fn substring_starting_from(text: &'static str, query: &str) -> &'static str {
fn substring_starting_from(text: &'static str, query: &'static str) -> &'static str {

// компилационна грешка:
// fn substring_starting_from(text: &str, query: &'static str) -> &'static str {

(Реторичен) Въпрос

Какъв е "правилния начин"? Минимални ограничения

fn substring_starting_from(text: &'a str, query: &str) -> &'a str {
    match text.find(query) {
        Some(index) => &text[index..],
        None => "",
    }
}

Преговор

Lifetime анотации за функции

Връзка между резултата и параметрите

// Резултата е свързан с първия аргумент
fn substring_starting_from(text: &'a str, query: &str) -> &'a str {

// Резултата е свързан и с двата аргумента (може кой да е от двата да е резултат)
fn longest(first: &'a str, second: &'a str) -> &'a str {

Преговор

Lifetime elision

За всеки пропуснат lifetime в аргументите се добавя lifetime параметър

fn print(s: &str);                                  // elided
fn print<'a>(s: &'a str);                           // expanded

fn foo(x: (&u32, &u32), y: usize);                  // elided
fn foo<'a, 'b>(x: (&'a u32, &'b u32), y: usize);    // expanded

Преговор

Lifetime elision

Ако за аргументите има само един lifetime параметър (експлицитен или пропуснат), този lifetime се налага на всички пропуснати lifetimes в резултата

fn substr(s: &str, until: usize) -> &str;                         // elided
fn substr<'a>(s: &'a str, until: usize) -> &'a str;               // expanded

fn split_at(s: &str, pos: usize) -> (&str, &str);                 // elided
fn split_at<'a>(s: &'a str, pos: usize) -> (&'a str, &'a str);    // expanded

Преговор

Lifetime elision

Ако първият аргумент е &self или &mut self, неговият lifetime се налага на всички пропуснати lifetimes в резултата

fn get_mut(&mut self) -> &mut T;                                // elided
fn get_mut<'a>(&'a mut self) -> &'a mut T;                      // expanded

fn args(&mut self, args: &[T]) -> &mut Self;                    // elided
fn args<'a, 'b>(&'a mut self, args: &'b [T]) -> &'a mut Self;   // expanded

Преговор

Lifetime elision

Във всички останали случаи е грешка да пропуснем lifetime-а.

fn get_str() -> &str;                     // комп. грешка
fn longest(x: &str, y: &str) -> &str;     // комп. грешка

Преговор

Конвертиране

Конвертиране

struct Celsius(f64);
struct Fahrenheit(f64);
struct Kelvin(f64);

fn room_temperature() -> Fahrenheit {
    Fahrenheit(68.0)
}

Конвертиране

struct Celsius(f64);
struct Fahrenheit(f64);
struct Kelvin(f64);

fn room_temperature() -> Fahrenheit {
    Fahrenheit(68.0)
}

fn energy_to_heat_water(from: Kelvin, to: Kelvin, mass: f64) -> f64 {
    // whatever
}

Конвертиране

From

impl From<Celsius> for Kelvin {
    fn from(t: Celsius) -> Kelvin { Kelvin(t.0 + 273.15) }
}

impl From<Fahrenheit> for Celsius {
    fn from(t: Fahrenheit) -> Celsius { Celsius((t.0 - 32) / 1.8) }
}

impl From<Fahrenheit> for Kelvin {
    fn from(t: Fahrenheit) -> Kelvin { Kelvin::from(Celsius::from(t)) }
}

Конвертиране

From

Сега вече можем да си сварим яйца

let e = energy_to_heat_water(Kelvin::from(room_temperature()), Kelvin::from(Celsius(100.0)), 1.0);
println!("Heating water will cost {}J", e);

Конвертиране

From

pub trait From<T> {
    fn from(T) -> Self;
}

Конвертиране

Into

pub trait Into<T> {
    fn into(self) -> T;
}

Конвертиране

Into

// използвайки From
let e = energy_to_heat_water(Kelvin::from(room_temperature()), Kelvin::from(Celsius(100.0)), 1.0);

// използвайки Into
let e = energy_to_heat_water(room_temperature().into(), Celsius(100.0).into(), 1.0);

Конвертиране

Generics

Честа практика е библиотечни функции да не взимат T, а нещо което може да се конвертира до T

fn energy_to_heat_water<T1, T2>(from: T1, to: T2, mass: f64) -> f64  where
    T1: Into<Kelvin>,
    T2: Into<Kelvin>
{
    // whatever
}

let e = energy_to_heat_water(room_temperature(), Celsius(100.0), 1.0);

Iterator

pub trait Iterator {
    /// Типа на елементите, по които циклим
    type Item;

    /// Мърда итератора напред и връща следващата стойност
    fn next(&mut self) -> Option<Self::Item>;

    // ...
}

Iterator

Fibonacci

pub struct Fibonacci {
    previous: u32,
    current: u32,
}

impl Fibonacci {
    fn new() -> Self {
        Fibonacci { previous: 1, current: 0 }
    }
}

Iterator

Fibonacci

impl Iterator for Fibonacci {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        let next = self.previous + self.current;

        self.previous = self.current;
        self.current = next;

        Some(next)
    }
}

Iterator

Fibonacci

fn main() {
    let mut fibs = Fibonacci::new();

    for n in fibs.take(10) {
        print!("{}", n);
    }
}
1 1 2 3 5 8 13 21 34 55 

Iterator

Fibonacci

fn main() {
    let fibs = Fibonacci::new();
    let fibs_vector = fibs.take(10).collect::<Vec<u32>>();

    println!("{:?}", fibs_vector);
}
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 

Iterator

Fibonacci

fn main() {
    let fibs = Fibonacci::new().
        take(10).
        collect::<Vec<u32>>();

    println!("{:?}", fibs);
}
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 

Iterator

TextInfo

pub struct TextInfo<'a> {
    text: &'a str,
}

Iterator

TextInfo

pub struct TextInfo { /* ... */ }

struct SentenceIterator<'a> {
    chars: str::Chars<'a>,
    in_sentence: bool
}

impl<'a> SentenceIterator<'a> {
    fn new(s: &'a str) -> Self {
        Self {
            chars: s.chars(),
            in_sentence: false
        }
    }
}

Iterator

TextInfo

impl<'a> Iterator for SentenceIterator<'a> {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        for c in &mut self.chars {
            match c {
                '?' | '!' | '.' => if self.in_sentence {
                    self.in_sentence = false;
                    return Some(c);
                },
                _ => if !c.is_whitespace() { self.in_sentence = true; }
            }
        }
        None
    }
}

Iterator

TextInfo

pub fn emotion(&self) -> String {
    let mut exclamations = 0;
    let mut questions = 0;
    let mut full_stops = 0;

    for c in self.sentence_endings() {
        match c {
            '!' => exclamations += 1,
            '?' => questions    += 1,
            '.' => full_stops   += 1,
            _   => panic!(format!("Sentence ending was {}", c))
        }
    }

    // ...
}

Iterator

Methods

Итераторите поддържат някои известни методи от функционалното програмиране:

Iterator

enumerate

Използвайте enumerate вместо броячи

for (i, element) in ['a', 'b', 'c'].into_iter().enumerate() {
    println!("arr[{}] = {}", i, element);
}
arr[0] = a
arr[1] = b
arr[2] = c 

Iterator

map

Приема функция, която преобразува всеки елемент от итератора

fn transform(ch: char) -> char {
    // Работи правилно само за ascii символи
    (ch as u8 + 1) as char
}

for element in vec!['a', 'b', 'c'].into_iter().map(transform) {
    println!("{}", element);
}
b
c
d 

Iterator

filter

Филтрира елементите на итератора, като премахва тези които не отговарят на предиката

fn predicate(num: &u32) -> bool {
    num.is_power_of_two()
}

for element in vec![1, 2, 3, 4, 5, 6, 7, 8].into_iter().filter(predicate) {
    println!("{}", element);
}
1
2
4
8 

Iterator

fold

'Смачква' стойностите на итератора в една

fn multiply(acc: u32, num: u32) -> u32 {
    acc * num
}

let factorial = vec![1, 2, 3, 4].into_iter().fold(1, multiply);

println!("{}", factorial);
24 

Iterator

closures

Естествено има по-лесен начин за използване, чрез closures

Iterator

closures

for element in vec![5, 10, 15].into_iter().map(|x| x/5) {
    println!("{}", element);
}
1
2
3 

Iterator

Мързеливост

Итераторите са мързеливи, т.е не правят нищо докато не извикваме финализираща функция или next

Iterator

Мързеливост

fn main() {
    [1, 2, 3, 4].into_iter().take(10);
}
warning: unused `std::iter::Take` which must be used:
iterator adaptors are lazy and do nothing unless consumed
--> src/main.rs:2:5
  |
2 |     [1, 2, 3, 4].into_iter().take(10);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: #[warn(unused_must_use)] on by default 

Iterator

Мързеливост

Специални итератори

IntoIterator

pub trait IntoIterator {
    /// Типа на елементите, по които циклим
    type Item;

    /// В какъв итератор го превръщаме?
    type IntoIter: Iterator<Item=Self::Item>;

    /// Създаваме итератор от стойност
    fn into_iter(self) -> Self::IntoIter;
}

IntoIterator

Имплементиран е за всички типове които имплементират trait Iterator

impl<I: Iterator> IntoIterator for I {
    type Item = I::Item;
    type IntoIter = I;

    fn into_iter(self) -> I {
        self
    }
}

IntoIterator

Но защо?? 🤔

IntoIterator

for-loop

let values = vec![1, 2, 3, 4, 5];

for x in values {
    println!("{}", x);
}
let values = vec![1, 2, 3, 4, 5];

{
    let mut iter = IntoIterator::into_iter(values);
    loop {
        match iter.next() {
            Some(val) => {
                let x = val;
                { println!("{}", x); };
            }
            None => break,
        };
    }
}

IntoIterator

Fibonacci

// Нека предположим, че някаква библиотека ни е дала тази структура
// и числата в нея са коректни
struct FibNums(u32, u32);

impl IntoIterator for FibNums {
    type Item = u32;
    type IntoIter = Fibonacci;

    fn into_iter(self) -> Self::IntoIter {
        Fibonacci {
            previous: self.0,
            current: self.1
        }
    }
}

IntoIterator

Fibonacci

Тогава може да започнем цикъла от определени числа.

for n in FibNums(5, 8).into_iter().take(10) {
    println!("{}", n);
}

IntoIterator

Fibonacci

Горният пример може да се постигне и с нормален from конструктор, но нямаше да може да се изпълни следният код:

for n in FibNums(5, 8) {
    println!("{}", n);
}

Kакво ще направи този пример?

IntoIterator

Fibonacci

Ще принтира числа докато не получим

'main' panicked at 'attempt to add with overflow'

IntoIterator

IntoIterator

Vector

impl<T> IntoIterator for Vec<T>
impl<'a, T> IntoIterator for &'a Vec<T>
impl<'a, T> IntoIterator for &'a mut Vec<T>

Тези имплементации ще връщат съответно

fn into_iter(self) -> std::vec::IntoIter<T>
fn into_iter(self) -> std::slice::Iter<'a, T>
fn into_iter(self) -> std::slice::IterMut<'a, T>

Всяка от структурите съдържа указатели към текущите елементи. Вътрешно into_iter за Iter и IterMut използва функциите iter и iter_mut които са ограничени от lifetimes, затова итераторите не живеят по-дълго от вектора.

IntoIterator

Vector

for element in vec.into_iter() {}
for element in vec.iter() {}
for element in vec.iter_mut() {}
for element in vec {}
for element in &vec {}
for element in &mut vec {}

ExactSizeIterator

Някои итератори знаят колко пъти ще итерират

pub trait ExactSizeIterator: Iterator {
    fn len(&self) -> usize { ... }
    fn is_empty(&self) -> bool { ... }
}

ExactSizeIterator

Например всяка крайна област знае колко пъти ще се итерира

let five = 0..5;

assert_eq!(5, five.len());

DoubleEndedIterator

Понякога са ни известни двата края на итератора и съответно може да итерираме и от двете страни

pub trait DoubleEndedIterator: Iterator {
    fn next_back(&mut self) -> Option<Self::Item>;

    fn rfind<P>(&mut self, predicate: P) -> Option<Self::Item>
    where
        P: FnMut(&Self::Item) -> bool,
    { ... }
}

DoubleEndedIterator

Един такъв пример са векторите

let numbers = vec![1, 2, 3, 4];

let mut iter = numbers.into_iter();

assert_eq!(Some(1), iter.next());
assert_eq!(Some(4), iter.next_back());
assert_eq!(Some(3), iter.next_back());
assert_eq!(Some(2), iter.next());
assert_eq!(None, iter.next());
assert_eq!(None, iter.next_back());

DoubleEndedIterator

Този тип итератор ни позволява да използваме rev метода на Iterator

let a = vec![1, 2, 3];

let mut iter = a.into_iter().rev();

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

Въпроси