В этой статье речь пойдет о том, почему использовать метод unwrap() для типов Result в продакшн коде Rust крайне нежелательно.

Rust как язык программирования был спроектирован с особым упором на безопасность и эффективность. Одним из ключевых способов достижения этого является система типов, которая позволяет разработчикам выявлять и обрабатывать ошибки во время компиляции, а не полагаться на проверки во время выполнения.

Одно из средств, которые Rust использует для обработки ошибок, — это тип Result, который может представлять успешный (вариант Ok) или неудачный (вариант Err) результаты. Метод unwrap() является удобным способом извлечения значения из типа Result в тех случаях, когда вы ожидаете, что операция завершится успешно. Однако, использование unwrap() в продакшн коде может быть опасным, и его следует избегать.

Вот несколько причин, почему вы никогда не должны использовать unwrap() для Result типов в продакшн коде:

Он может маскировать ошибки

Одна из основных проблем использования unwrap() заключается в том, что он может маскировать ошибки. Если значение Result окажется вариантом Err, то результатом вызова unwrap() будет паника, которая может закрашить всю программу. Это не только ужасно с точки зрения пользователя, но также не помогает в понимании и отладке проблемы.

Вместо того чтобы использовать unwrap(), вам следует явно обработать ошибку. Например, вы можете использовать для значения типа Result конструкцию match и обрабатывать варианты Ok и Err отдельно:

match result {
    Ok(value) => {
        // делаем что-то со значением
    },
    Err(error) => {
        // обрабатываем ошибку
    }
}

Он может скрывать баги

Еще одна проблема с использованием unwrap() заключается в том, что он также может скрыть баги в вашем коде. Например, если значением Result является вариант Err, но вы нигде его не отрабатываете, вы можете даже не понять, что что‑то пошло не так. Это может привести к незаметным, трудно обнаруживаемым ошибкам, которые бывает очень трудно отследить.

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

Он может сделать ваш код куда менее читабельным

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

Чтобы сделать ваш код более читабельным, вы всегда должны явно обрабатывать ошибки и предоставлять четкие и краткие комментарии, объясняющие, что вы делаете и почему. Это не только облегчит понимание вашего кода, но также упростит его сопровождение и отладку.

Он может привести к непредсказуемому поведению

Еще одна проблема с использованием unwrap() заключается в том, что это может стать причиной непредсказуемого поведения. Если значением Result является вариант Err, следствием вызова unwrap() будет паника, которая может иметь непредвиденные последствия. Например, если вы используете unwrap() в многопоточной среде паника может привести к сбою или непредсказуемому поведению других потоков.

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

Он может снизить производительность

Наконец, использование unwrap() может снизить производительность вашего кода, так как поскольку unwrap() вызывает панику для Err значений, он должен выполнить проверку во время выполнения, чтобы определить, является ли значение Ok или Err. Это может увеличить потребление ресурсов вашим кодом, особенно если вы вызываете unwrap() более одного раза или в критических для производительности частях вашего кода.

Чтобы оптимизировать производительность вашего кода, вы должны избегать использования unwrap() и, как я уже неоднократно здесь повторял, явно обрабатывать ошибки. Это позволит вам избежать излишних проверок во время выполнения и писать более эффективный код.

Заключение

В заключение, вы никогда не должны использовать метод unwrap() для типа Result в продакшн коде Rust. Хотя в некоторых случаях это может быть очень удобно, это может маскировать ошибки, скрывать баги, делать ваш код менее читабельным, приводить к непредсказуемому поведению и снижению производительности. Вместо этого вы должны явно обрабатывать ошибки и проектировать свой код таким образом, чтобы он был надежным, удобным в сопровождении и эффективным.


Через несколько дней состоится открытый урок, посвященный Rust разработке. На занятии рассмотрим наиболее популярные направления деятельности, которые может выбрать Rust разработчик. Разберёмся, чем предстоит заниматься по каждому из направлений, также обсудим вакансии и требования к ним. Занятие будет полезно начинающим разработчикам. Записаться можно по ссылке.

Комментарии (18)


  1. ijsgaus
    00.00.0000 00:00
    +22

    Снижение производительности - вероятно, во всех других случаях обработки Result мы никогда не выполняем проверку на Ok или Err (то есть это замечание - явная ложь).

    Непредсказуемое поведение - не могу придумать ни одного примера.

    Отчего это читабельность кода снизится? Я вот совсем не вижу этого снижения.

    Паника - не трудноотслеживаемая ошибка ни разу. И ошибки она никак не маскирует. В общем вся статья - ошибка от начала и до конца. Единственно, желательно вместо unwrap использовать expect - тогда сообщение паники можно сделать более содержательным.


    1. amishaa
      00.00.0000 00:00

      В целом я согласен с комментарием, в тексте действительно много мусора.
      Но в первом случае (про перформанс) автор всё-таки, вероятно, прав.

      for _ in 1..1001 {
        do_something(maybe_int.unwrap());
      }

      делает unwrap 1000 раз, а

      if let Some(int) = maybe_int {
        for _ in 1..10001 {
          do_something(int);
        }
      }

      всего один


      1. PROgrammer_JARvis
        00.00.0000 00:00
        +6

        Ну так ведь это не эквивалентное сравнение. Можно же сделать

        let int = maybe_int.unwrap();
        for _ in 1..1001 {
          do_something(int);
        }
        


        1. amishaa
          00.00.0000 00:00
          +1

          Да, конечно.

          Общая рекомендация "делайте проверку на err в коде один раз на переменную" (что дословно написано в тексте), или, даже, "парсите, а не валидируйте" - выглядит верной в общем случае (не смотря на то, что остальные тезисы в статье откровенно слабые).


      1. JustForFun88
        00.00.0000 00:00
        -1

        Этоивсе сведется к одному и тому же ассемблеру, так что не принципиально. Компилятор все оптимизирует


    1. MaNaXname
      00.00.0000 00:00

      Т.е. если Вы анврапаете результат получения данных с базы и она легла - то не будет проблем? А если файл вдруг не читается из-за лока другим процесом? Примеров можно привести кучу.


  1. freecoder_xx
    00.00.0000 00:00

    Главное правило для использования unwrap против expect заключается в том, что unwrap должен использоваться только в том случае, если программа гарантирует отсутствие в этом месте отрицательного значения, поэтому паника никогда не произойдёт. Можно ещё в комментарии .unwrap(/* ... */) написать явно, почему тут не будет паники.


    1. orekh
      00.00.0000 00:00
      +5

      ...зачем писать комментарий, если можно написать его в .expect("текст объясняющий почему паника здесь не произойдет")?


      1. freecoder_xx
        00.00.0000 00:00

        Можно. Но как тогда вы отличите те места, где паника потенциально может произойти от тех, где она произойти никак не должна? Придётся вчитываться во все тексты сообщений. А unwrap - сразу видно.


        1. qw1
          00.00.0000 00:00
          +2

          Не понимаю, почему unwrap «сразу видно», а expect — нет?
          В чём разница

          let filename = args.get(1).unwrap(/* отсутствует обязательный параметр */);

          let filename = args.get(1).expect("отсутствует обязательный параметр");


          1. amishaa
            00.00.0000 00:00
            +2

            В ожиданиях. Второй - это место с ожидаемым падением (если входные данные не корректны).
            А unwrap предлагается использовать в каких-то таких случаях:

            if (args.len() == 2 || args.len() == 5) {
              let filename = args.get(1).unwrap(/*just checked len > 1*/)
            <...>
            }

            Конкретно у get есть версия, которая просто паникует (get_unchecked), но такой аналог есть не у всех функций возвращающих option.


            1. qw1
              00.00.0000 00:00

              Да, теперь понял. В тех местах, где программист уверен, предпочтительнее unwrap, потому что непонятно, что писать в сообщении.


            1. Cerberuser
              00.00.0000 00:00
              +4

              Конкретно у get есть версия, которая просто паникует

              Не паникует, а предполагает, что программист только что всё проверил другими способами (как следствие - требует unsafe в качестве подтверждения).


        1. NekoiNemo
          00.00.0000 00:00
          +2

          Так expect-то он тоже не для повседневного использования. По-хорошему, expect тоже нельзя использовать если есть альтернативы, а только там где он никогда не должен вызваться в рантайме, т.к. в большинстве случаев программа не должна паниковать, а должна пузырьковать ошибку обратно в main() и там грациозно завершаться с ошибочным exit code.


    1. Miiao
      00.00.0000 00:00

      Для ситуаций, когда ошибки гарантировано не будет, существует unwrap_unchecked, unwrap и expect же для тех ситуаций, в которых программа при ошибке вообще не сможет корректно работать.


      1. IronKaput
        00.00.0000 00:00
        -1

        Для ситуаций, когда ошибки гарантировано не будет, существует unwrap_unchecked

        Сегодня гарантии, а завтра UB


  1. Stalkerx777
    00.00.0000 00:00
    +2

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

    Для пользователя может и да, но найти причину такого краша с RUST_BACKTRACE=1 секундное дело. И конечно же grep unsafe|unwrap|except

    это может маскировать ошибки, скрывать баги, делать ваш код менее читабельным, приводить к непредсказуемому поведению и снижению производительности

    Маскировать ошибки и баги? Вы о чём? Объясните пожалуйста как паника "скрывает" баги? Что непредсказуемого в панике? Почему снижается производитель? (пример с unwrap() в hot loop не берём)

    Ошибки нужно обрабатывать, это верно, но то что вы написали в статье не соответствует действительности.


  1. nanolsn
    00.00.0000 00:00
    +3

    А ведь эти же люди будут учить кого-то расту.

    Интересно, на самом деле, посмотреть на примеры кода, а не на абстрактное "не используйте unwrap". Показали бы пример с unwrap, а потом то, как его избежать.

    В целом с общим посылом статьи я полностью согласен: не используйте unwrap, потому как есть более содержательный expect. В эту функцию принято передавать сообщение об ожидаемом поведении, чтобы если вдруг возникнет паника, можно было бы быстро понять в чём проблема.

    Избегать unwrap/expect мало смысла. Если вы пишете хоть сколько-то полезный код, в нём всё равно есть много операций, которые паникуют (скажем, деление на 0 или обращение по несуществующиму ключу в хешмау через [] оператор). Если код паникует, то это считается ошибкой программиста. Но это не значит, что ошибка в том, что программист написал unwrap, скорее в том, что он нарушил инвариант программы и ему стоит исправить код. unwrap/expect это полезные функции, так как позволяют понять где возникла ошибка, чтобы потом её исправить. Если в вашем коде нарушена логика работы, то прятать её под ковёр, вместо того, чтобы как можно скорее сообщить об ошибке - только сделает систему менее предсказуемой и менее надёжной.

    Не следует путать ошибку программиста и штатное возвращение Result::Err

    Единственный случай, когда избегать любую панику полезно, если вам действительно требуется полностью надёжная система, которая никогда не упадёт. В таком случае при возникновении бага лучше будет вернуть Result::Err но это редкий случай относительно обычного прикладного кода. Даже при использовании раста в ffi, вместо избегания паники лучше её перехватывать.

    В целом, любая "обработка ошибок" должна быть разумна. Раст не просто так предоставляет функционал паники. Конечно, если возможно какие-то разумно обработать Err или None, то это нужно сделать. Но паника при получении некорректного результата, это тоже обработка ошибок. Если в программе произошло то, что не должно было произойти, паника - это самый разумный выбор