Какая польза от использования результата?

Я не понимаю, почему Result существует в Rust. Я могу видеть, как Option может быть полезен, но использование Result просто усложняет код без необходимости.

Рассмотрим следующий пример:

#[derive(PartialEq, Eq, Debug)]
enum MyErr {
    None,
    FailOne,
}

fn returns_tuple() -> (u8, MyErr) {
    // (1, None) // <-- Success path
    (0, MyErr::FailOne)
}

fn returns_result() -> Result<u8, MyErr> {
    // Ok(1) // <-- Success path
    Err(MyErr::FailOne)
}

#[test]
fn test_check_return_values() {
    let x = returns_result();
    if x.is_ok() {
        println!("result: Is OK: {}", x.unwrap()); // <-- Must use unwrap
    } else {
        match x.err().unwrap() { // <-- Again, unwrapping
            MyErr::None => {}, // Required for match
            MyErr::FailOne => println!("result: Failed One"),
        }
    }
}

#[test]
fn test_check_return_values_2() {
    let (y, err) = returns_tuple();
    match err {
        MyErr::None => println!("tuple: Is OK: {}", y),
        MyErr::FailOne => println!("tuple: Failed one"),
    }
}

Единственное, что я вижу, это то, что он незначительно увеличивает удобство написания функций, так как вы можете просто вызвать Ok() и Err() для возврата результатов.

Я видел, как некоторые люди говорили об этом, поэтому вы можете использовать условия, но это не совсем так; вы можете использовать условия отлично, используя кортежи. (Примечание - "условия" были особенностью Rust, которые были удалены до 1.0)

Я также видел, как некоторые люди говорили, что Result более эффективен, возвращая кортеж, но Result является кортежем, поэтому я не вижу, как это может быть.

Ответы

Ответ 1

Рассмотрим определение Result:

/// `Result` is a type that represents either success (`Ok`) or failure
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use]
pub enum Result<T, E> {
    /// Contains the success value
    Ok(T),

    /// Contains the error value
    Err(E)
}

Дистиллированный к битам, которые имеют значение, enum Result<T, E> { Ok(T), Err(E) }.

Это не кортеж (T, E); скорее, это либо T (ОК), либо E (ошибка).

Если вы используете кортеж (T, E), вы должны определить как T, так и E. Для вашего returns_tuple это означало определение 0 в качестве магического значения и добавление нового варианта в перечисление MyErr, None. None не является ошибкой; семантически необоснованно моделировать его таким образом. Он также затем распространяется в другие места из-за требования исчерпывающего соответствия.

Когда вы имеете дело с более сложными типами, определение фиктивного значения может быть менее осуществимым или более дорогостоящим. Как обобщение, наличие фиктивных ценностей не является хорошим планом. Это слишком вероятно, что где-то по дорожке вы на самом деле попытаетесь их использовать.

У Rust есть хорошая система типов, которая позволяет избежать таких проблем.

Мне кажется, что вы пропустили силу подбора Rust; на самом деле, единственный способ получить значение из перечисления - сопоставление шаблонов; в результате такие вещи, как Result.ok(), Result.err() и Option.unwrap(), реализуются с точки зрения соответствия шаблонов.

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

#[derive(PartialEq, Eq, Debug)]
enum MyErr {
    // Now we don't need that phoney None variant.
    FailOne,
}

fn returns_result() -> Result<u8, MyErr> {
    Err(MyErr::FailOne)
}

#[test]
fn test_check_return_values() {
    match returns_result() {
        Ok(num) => println!("result: Is OK: {}", num),
        Err(MyErr::FailOne) => println!("result: Failed One"),
    }
}