Rust - Grundlagen - 12 - Iteratoren und Closures

Iteratoren und Closures

Iteratoren und Closures gehören zu den Grundideen der funktionalen Programmierung.

Verständnis

  • Closures

    Darstellung: Eine Funktion kann einer Variable zugewiesen werden, ohne sie aufzurufen.

    Sie sind für Aufrufer verborgen, kurz und eindeutig.

    Der Compiler leitet ab, welcher Closure-Trait verwendet wird:

    FnOnce  taking ownership (move)

    FnMut  borrow mutably

    Fn  borrow immutably

    Die Umgebung (environment) sind die äußeren Variablen, die eine Closure verwendet.

    // 常见声明
    let expensive_closure = |num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };
    
    // 只有 a single expression 的时候
    let add_one_v3 = |x|             { x + 1 };
    // 大括号可以去掉
    let add_one_v4 = |x|               x + 1  ;
    
    // closures 自带 type inferred
    let example_closure = |x| x;
    let s = example_closure(String::from("hello"));
    let n = example_closure(5);  // 编译器处理到这里的时候会报错
    
    // 使用 struct + closures + trait 实现 lazy load
    struct Cacher<T>
    where
        T: Fn(u32) -> u32,
    {
        calculation: T,
        value: Option<u32>,
    }
    impl<T> Cacher<T>
    where
        T: Fn(u32) -> u32,
    {
        fn new(calculation: T) -> Cacher<T> {
            Cacher {
                calculation,
                value: None,
            }
        }
    
        fn value(&mut self, arg: u32) -> u32 {
            match self.value {
                Some(v) => v,
                None => {
                    let v = (self.calculation)(arg);
                    self.value = Some(v);
                    v
                }
            }
        }
    }
    
    // 通过 move 关键字强制指定
    // 一般不会这么做(交给编译器推导)
    // 以下代码无法通过编译(因为 x 被 move 了)
    fn main() {
        let x = vec![1, 2, 3];
    
        let equal_to_x = move |z| z == x;
    
        println!("can't use x here: {:?}", x);
    
        let y = vec![1, 2, 3];
    
        assert!(equal_to_x(y));
    }
    
  • Iteratoren

    Iteration und Schleifen haben keinen Leistungsunterschied.

    // 基础写法
    #[test]
    fn iterator_demonstration() {
        let v1 = vec![1, 2, 3];
    		// 由于后续需要 next,会修改 v1 迭代器状态,故需要 mut 关键字
        // 如果需要 take ownership,则使用 into_iter
    		// 如果需要 mutable referance,则使用 iter_mut
        let mut v1_iter = v1.iter();
    
        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);
    }
    
    let v1: Vec<i32> = vec![1, 2, 3];
    // 这里没有 consuming adaptors(调用 next 的 methods)会触发 compile warning
    v1.iter().map(|x| x + 1);
    let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); // 使用 collect 消费迭代器
    assert_eq!(v2, vec![2, 3, 4]);
    
    /// into_inter 使用示例 - filter
    #[derive(PartialEq, Debug)]
    struct Shoe {
        size: u32,
        style: String,
    }
    fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
        shoes.into_iter().filter(|s| s.size == shoe_size).collect()
    }
    
    // 实现一个能 iter 的 struct
    struct Counter {
        count: u32,
    }
    impl Counter {
        fn new() -> Counter {
            Counter { count: 0 }
        }
    }
    impl Iterator for Counter {
        type Item = u32;
    
        fn next(&mut self) -> Option<Self::Item> {
            if self.count < 5 {
                self.count += 1;
                Some(self.count)
            } else {
                None
            }
        }
    }
    #[test]
    fn calling_next_directly() {
        let mut counter = Counter::new();
    
        assert_eq!(counter.next(), Some(1));
        assert_eq!(counter.next(), Some(2));
        assert_eq!(counter.next(), Some(3));
        assert_eq!(counter.next(), Some(4));
        assert_eq!(counter.next(), Some(5));
        assert_eq!(counter.next(), None);
    }
    
    // 使用其它 Iterator Trait Methods
    #[test]
    fn using_other_iterator_trait_methods() {
        let sum: u32 = Counter::new()
            .zip(Counter::new().skip(1))  // 任一 iter 返回 None,zip 停止 pair items,所以这里只有 4 pairs
            .map(|(a, b)| a * b)
            .filter(|x| x % 3 == 0)
            .sum();
        assert_eq!(18, sum);
    }
    

Quelle

https://doc.rust-lang.org/book/ch13-00-functional-features.html

…

Typinferenz und Annotationen von Closures

Closures benötigen wie fn-Funktionen normalerweise keine Typannotation für Parameter oder Rückgabewert. Funktionen brauchen Annotationen, weil sie eine explizite Schnittstelle darstellen. Closures werden dagegen in Variablen gespeichert und verwendet, ohne sie zu benennen oder Bibliotheksnutzern offenzulegen. Da sie meist kurz und nur in einem engen Kontext relevant sind, kann der Compiler ihre Typen zuverlässig ableiten.

Die Umgebung mit Closures erfassen

Closures können ihre Umgebung erfassen und auf Variablen des Gültigkeitsbereichs zugreifen. Dafür speichern sie die Werte im Speicher. Sie können Werte auf drei Arten erfassen: Besitz übernehmen, veränderlich borgen oder unveränderlich borgen. Das entspricht den Traits FnOnce, FnMut und Fn.

FnOnce verbraucht die erfassten Variablen und kann deshalb nur einmal aufgerufen werden. FnMut kann die Umgebung durch veränderliches Borrowing ändern. Fn borgt Werte unveränderlich. Alle Closures implementieren FnOnce; Closures ohne Move implementieren zusätzlich FnMut, und solche ohne veränderlichen Zugriff auch Fn.

Mit dem Schlüsselwort move vor der Parameterliste kann man erzwingen, dass eine Closure Besitz übernimmt. Das ist besonders nützlich beim Übergeben an einen neuen Thread. Eine move-Closure kann trotzdem Fn oder FnMut implementieren; entscheidend ist, was sie mit den erfassten Werten tut.

Wenn x durch move in die Closure verschoben wird, besitzt die Closure x, und main darf x danach nicht mehr verwenden. Entfernt man die println!-Zeile, kompiliert das Beispiel. Bei Trait Bounds beginnt man meist mit Fn; der Compiler weist darauf hin, wenn FnMut oder FnOnce nötig ist.