Skip to Content

Domknięcia

Literały funkcyjne

  1. Literały funkcyjne opisują sposób definiowania funkcji anonimowych (obiektów funkcyjnych), które mogą być później przypisane do zmiennych lub przekazane jak parametr wywowałania innej funkcji (tzw. funkcji wyższego rzędu).
  2. Przykłady definicji funkcji anonimowej za pomocą literałów o różnym poziomie szczegółowości:
    let increment_v1 = |x: u32| -> u32 { x + 1 }; let increment_v2 = |x| { x + 1 }; let increment_v3 = |x| x + 1; // only if there is only one expression assert_eq!(increment_v1(4), 5); // calling functional object assert_eq!(increment_v2(4), 5); assert_eq!(increment_v3(4), 5);
  3. Skąd Rust “wie” jakiego typu argumenty przyjmują literały funkcyjne? Spróbuj zakomentować ostatnie linie i zobaczyć wyniki kompilacji.

Domknięcia

  1. Domknięcie (ang. clousure) pozwala w funkcji anonimowej wykorzystać kontekst, w którym jest ona tworzona. Kontekst ten jest przechowywany w obiekcie funkcyjnym (pożyczany przez obiekt funkcyjny).
    let mut x = 5; let mul_5 = |a| a * x; assert_eq!(mul_5(4), 20);
  2. Domknięcie może również modyfikować wartości zapożyczone z kontekstu (w takim wypadku mówimy o mutowalnej pożyczce)
    let mut x = 5; let mut add_to_x = |a| x += a; add_to_x(5); add_to_x(1); assert_eq!(x, 11); x += 1; assert_eq!(x, 12);
  3. Do domknięcia można przenieść również własność wartości, która ma być pobrana z kontekstu
    let mut x = vec![1, 2, 3]; let equal_to_x = move |y| y == x; // equal_to_x becomes the owner of x // println!("can't use x here: {:?}", x); let y = vec![1, 2, 3]; assert!(equal_to_x(y));

Iterator

Domknięcia wykorzystywane są najczęściej wraz z iteratorami do efektywnego przetwarzania elementów z kolekcji. Cecha Iterator definiuje szereg metod umożliwiających pracę na kolejnych elementach produkowanych przez iterator. W języku Rust iteratory są leniwe, co oznacza, że nie dają żadnego efektu, dopóki nie wywołamy metod, które “zużywają” iterator.

  1. Podstawowy interfejs iteratora pozwala na przechodzenie do kolejnych jego elementów za pomocą metody next:

    let v = vec![1, 2, 3]; let mut iter = v.iter(); // must be mutable assert_eq!(Some(&1), iter.next()); assert_eq!(Some(&2), iter.next()); assert_eq!(Some(&3), iter.next()); assert_eq!(None, iter.next());

    Metoda ta zwraca typ Option, który w przypadku wartości Some zawiera wskaźnik (pożyczkę), na element iteratora.

  2. Konsumpcja iteratora: w interfejsie iteratora wyróżniamy metody, które uruchamiają przetwarzanie kolejnych elementów. Metody te “zużywają” iteratora, co w praktyce oznacza, że przejmują własność do instancji iteratora, a w konsekwencji po ich wywołaniu, nie można się odwołać ponownie do iteratora. Do tego typu metod należą:

    • collect,
    • sum,
    • count
    let v = vec![1, 2, 3]; let iter = v.iter(); assert_eq!(6, iter.sum()); // consumes the iterator let v = vec![1, 2, 3]; assert_eq!(3, v.iter().count()); // consumes the iterator let a = [1, 2, 3]; let v = a.iter().collect::<Vec<_>>(); // turbofish syntax assert_eq!(&1, *v.get(0).unwrap()); // :>
  3. Znajdź w dokumentacji cechy Iterator inne metody, które konsumują interator. Czym charakteryzują się te metody?

  4. Metody przetwarzające elementy iteratora, to metody, które pozwalają przekształcić poszczególne elementy, które produkuje iterator. Do najczęściej używanych metod tego typu należą:

    • map,
    • filter,
    • flat_map,
    • zip.
    let v = vec![1, 2, 3]; // transforming each element let res = v.iter().map(|x| x * 2).collect::<Vec<i32>>(); assert_eq!(vec![2, 4, 6], res); // filter elements let res = v.iter().filter(|&x| *x % 2 == 0).map(|x| *x).collect::<Vec<i32>>(); assert_eq!(vec![2], res);
  5. Metody tworzące iterator to metody wywoływane na kolekcjach, które tworzą nową instancję iteratora zwracającego elementy danej kolekcji. Wyróżnia się 3 metody kreacyjne:

    • Metoda iter - tworzy nowy iterator, który pożycza niemutowalne wartości kolekcji
    • Metoda iter_mut - tworzy iterator, który pożycza mutowalne wartości kolekcji
    • Metoda into_iter - tworzy iterator, który przejmuje własność kolekcji (np. wektora), na którym tworzony jest iterator

    Przykład użycia metody iter_mut:

    let mut v = vec![1, 2, 3]; v.iter_mut().map(|x| *x +=1).collect::<Vec<_>>(); assert_eq!(vec![2, 3, 4], v);

    Przykład użycia metody into_iter:

    let v = vec![1, 2, 3]; let v2 = v.into_iter().map(|x | x * 2).collect::<Vec<_>>(); // v.get(0); // cannot use v anymore - it's moved assert_eq!(vec![2, 4, 6], v2);
Last updated on