O co chodzi z tym operatorem znaku zapytania?

Odpowiedzi:

149

Jak być może zauważyłeś, Rust nie ma wyjątków. Występują paniki, ale ich funkcjonalność jest ograniczona (nie mogą przenosić ustrukturyzowanych informacji), a ich użycie do obsługi błędów jest odradzane (są one przeznaczone do błędów nieodwracalnych).

W Rust do obsługi błędów używa się plików Result. Typowy przykład to:

fn halves_if_even(i: i32) -> Result<i32, Error> {
    if i % 2 == 0 {
        Ok(i / 2)
    } else {
        Err(/* something */)
    }
}

fn do_the_thing(i: i32) -> Result<i32, Error> {
    let i = match halves_if_even(i) {
        Ok(i) => i,
        Err(e) => return Err(e),
    };

    // use `i`
}

To jest świetne, ponieważ:

  • pisząc kod nie można przypadkowo zapomnieć o uporaniu się z błędem,
  • czytając kod, od razu widać, że tutaj istnieje potencjał błędu.

Jest jednak mniej niż idealny, ponieważ jest bardzo rozwlekły. Tutaj pojawia się operator znaku zapytania ?.

Powyższe można przepisać jako:

fn do_the_thing(i: i32) -> Result<i32, Error> {
    let i = halves_if_even(i)?;

    // use `i`
}

co jest o wiele bardziej zwięzłe.

To, ?co tutaj robi, jest równoważne z matchpowyższym stwierdzeniem. W skrócie: rozpakowuje Resultif OK i zwraca błąd, jeśli nie.

To trochę magiczne, ale obsługa błędów wymaga pewnej magii, aby zmniejszyć schemat, iw przeciwieństwie do wyjątków od razu widać, które wywołania funkcji mogą, a nie muszą, zawierać błędy: te, które są ozdobione ?.

Jednym z przykładów magii jest to, że działa to również w przypadku Option:

// Assume
// fn halves_if_even(i: i32) -> Option<i32>

fn do_the_thing(i: i32) -> Option<i32> {
    let i = halves_if_even(i)?;

    // use `i`
}

Jest to zasilane przez (niestabilną) Trycechę.

Zobacz też:

Matthieu M.
źródło
5
byłoby miło, gdybyś mógł trochę rozszerzyć swoją odpowiedź, np. przedyskutować, że zwracany typ funkcji musi pasować do typu, który próbujesz „rozpakować”, np . Resultlub Option.
cześć
@hellow Myślę, że lepiej byłoby, gdyby było zupełnie nowe pytanie
Paul Razvan Berg
2

Służy do propagacji błędu dla naprawialnego błędu typu Wynik <T, E>. Rozpakowuje wynik i podaje wartość wewnętrzną.

Zamiast obsługiwać przypadki błędu, propagujesz je do kodu wywołującego i zajmujesz się tylko przypadkiem OK. Zaletą jest to, że eliminuje wiele schematów i upraszcza implementację funkcji.

snnsnn
źródło
1
nie mylić z faktyczną .unwrap()paniką w przypadku błędu.
Jordan,