Typ Option

  1. Definicja typu oznaczającego wartość lub jej brak:
    enum Option<T> { // T depicts generic type 
        Some(T),
        None
    }
  2. Utworzenie wartości typu Option. W przypadku wartości pustej (None), konieczne jest zdefiniowanie typu, aby określić typ zawartości - kompilator nie może wydedukować tej informacji.
    fn main() {
        let some_number = Some(5);
        let some_text = Some(String::from("Some value in Option type"));
     
        let no_value : Option<i32> = None;
    }
  3. Jaka jest różnica pomiędzy typem Option a pustą wartością typu null? Spróbuj wykonać poniższy kod:
    fn main() {
        let x = Some(5);
        let y = 10;
     
        let sum = x + y;
    }

Typ Option reprezentuje wartość opcjonalną: każda opcja jest albo typu Some i zawiera wartość, albo typu None i jej nie zawiera. Typy Option są bardzo powszechne w kodzie Rust, ponieważ mają wiele zastosowań:

  • Wartości początkowe
  • Wartości zwracane dla funkcji, które nie są zdefiniowane w całym zakresie wejściowym (funkcje częściowe)
  • Wartość zwracana w przypadku zgłaszania prostych błędów, gdzie w przypadku błędu zwracana jest wartość None.
  • Opcjonalne pola struktury
  • Pola struktury, które można pożyczyć lub “sprzedać”
  • Opcjonalne argumenty funkcji
  • Wskaźniki puste

Źródło: Dokumentacja modułu Option

Wyłuskanie wartości z Option

  1. Wyłuskanie wartości za pomocą konstrukcji match:
    fn increment(n : Option<i32>) -> Option<i32>{
        match n {
            Some(n) => Some(n + 1),
            None => None
        }
    }
     
    fn main() {
        let n = increment(Some(5));
    }
  2. Wyłuskanie wartości za pomocą metod zglaszających błąd.
     
    fn main() {
        let n : Option<i32> = Some(5);
        let n : i32 = n.unwrap(); 
     
        let m : Option<String> = Some(String::from("I use Option"));
        let m : String = m.expect("This always should be a non empty String");
     
        let argh : Option<String> = None;
        let args = argh.unwrap(); // the program panics here
    }
    ⚠️

    Metody expect i unwrap powinny być używane tylko w celach testowych lub w sytuacji, gdy kontynuacja wykonania programu w przypadku braku wartości nie ma sensu.

  3. Jeśli wiesz jaka ma być domyślna wartość w przypadku braku wartości możesz wykorzystać metodę unwrap_or
     
    fn main() {
        assert_eq!(Some("car").unwrap_or("bike"), "car");
        assert_eq!(None.unwrap_or("bike"), "bike");
    }
  4. Możesz również wykorzystać domyślną wartość zdefiniowaną dla danego typu (typ ten musi realizować cechę Default)
     
    fn main() {
        let x: Option<u32> = None;
        let y: Option<u32> = Some(12);
     
        assert_eq!(x.unwrap_or_default(), 0);
        assert_eq!(y.unwrap_or_default(), 12);
    }

Operator ?

  1. Napiszmy funkcję dodającą dwie liczby całkowite, które mogą być opcjonalne:
    fn sum(x : Option<i32>, y : Option<i32>) -> Option<i32> {
        match (x, y) { // use tuple to avoid multiple matches
            (Some(x), Some(y)) => Some(x + y),
            _ => None
        }
    }
  2. Napisz funkcję main, która przetestuje działanie funkcji sumującej dwie wartości opcjonalne
  3. Wykorzystaj operator ? do uproszczenia powyższej funkcji:
    fn sum(x : Option<i32>, y : Option<i32>) -> Option<i32> {
        Some(x? + y?)
    }

Ćwiczenia

  1. Napisz metodę maybe_icecream, która zwróci ile porcji lodów zostało w lodówce (w zależności od pory roku). Napraw również test raw_value, aby poprawnie sprawdzał ilość porcji.

    // This function returns how much icecream there is left in the fridge.
    // If it's before 10PM, there's 5 scoops left. At 10PM, someone eats it
    // all, so there'll be no more left :(
    fn maybe_icecream(time_of_day: u16) -> Option<u16> {
        // We use the 24-hour system here, so 10PM is a value of 22 and 12AM is a
        // value of 0. The Option output should gracefully handle cases where
        // time_of_day > 23.
        // TODO: Complete the function body - remember to return an Option!
        todo!()
    }
     
    #[cfg(test)]
    mod tests {
        use super::*;
     
        #[test]
        fn check_icecream() {
            assert_eq!(maybe_icecream(0), Some(5));
            assert_eq!(maybe_icecream(9), Some(5));
            assert_eq!(maybe_icecream(18), Some(5));
            assert_eq!(maybe_icecream(22), Some(0));
            assert_eq!(maybe_icecream(23), Some(0));
            assert_eq!(maybe_icecream(25), None);
        }
     
        #[test]
        fn raw_value() {
            // TODO: Fix this test. How do you get at the value contained in the
            // Option?
            let icecreams = maybe_icecream(12);
            assert_eq!(icecreams, 5);
        }
    }
  2. Napraw poniższy kod, bez usuwania żadnej z linii. Wyjaśnij dlaczego obecna wersja się nie kompiluje.

    struct Point {
        x: i32,
        y: i32,
    }
     
    fn main() {
        let y: Option<Point> = Some(Point { x: 100, y: 200 });
     
        match y {
            Some(p) => println!("Co-ordinates are {},{} ", p.x, p.y),
            _ => panic!("no match!"),
        }
        y; // Fix without deleting this line.
    }

Źródło: rustlings