О чем этот оператор вопросительного знака?

Ответы:

145

Как вы могли заметить, в Rust нет исключений. У него есть паники, но их функциональность ограничена (они не могут нести структурированную информацию), и их использование для обработки ошибок не рекомендуется (они предназначены для неисправимых ошибок).

В Rust для обработки ошибок используется Result. Типичный пример:

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`
}

Это здорово, потому что:

  • при написании кода нельзя случайно забыть исправить ошибку,
  • при чтении кода вы сразу видите, что здесь есть вероятность ошибки.

Однако он далеко не идеален, поскольку очень многословен. Здесь на помощь ?приходит оператор вопросительного знака .

Вышесказанное можно переписать как:

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

    // use `i`
}

что намного короче.

То, ?что здесь означает, эквивалентно приведенному matchвыше утверждению. Вкратце: он распаковывает Resultif OK и возвращает ошибку, если нет.

Это немного волшебно, но для обработки ошибок требуется некоторая магия, чтобы сократить шаблон, и, в отличие от исключений, сразу видно, какие вызовы функций могут или не могут вызывать ошибки: те, которые украшены ?.

Одним из примеров волшебства является то, что это также работает для 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`
}

Это связано с (нестабильной) Tryчертой.

Смотрите также:

Матье М.
источник
5
было бы неплохо, если бы вы могли немного расширить свой ответ, например, обсудить, что тип возвращаемого значения функции должен соответствовать типу, который вы пытаетесь «развернуть», например Resultили Option.
Привет
@hellow Думаю, это лучше будет вообще новый вопрос
Пол Разван Берг
2

Он предназначен для распространения ошибки для типа исправимой ошибки Result <T, E>. Это раскрывает результат и дает вам внутреннюю ценность.

Вместо того, чтобы обрабатывать случай ошибки, вы распространяете его на вызывающий код и обрабатываете только случай Ok. Преимущество в том, что он устраняет множество шаблонов и упрощает реализацию функций.

snnsnn
источник
не путать с фактическим, .unwrap()который паникует в случае ошибки.
Иордания