Программисты на Go уже давно и долго жалуются на слишком многословную обработку ошибок. Все мы близко (а иногда и болезненно) знакомы со следующим шаблоном кода:

x, err := call()
if err != nil {
        // обработка err
}

Проверка if err != nil встречается настолько часто, что может становиться объёмнее остального кода. Обычно это происходит в программах, выполняющих много вызовов API, в которых обработка ошибок рудиментарна и они просто возвращаются. Некоторые программы в итоге выглядят примерно так:

func printSum(a, b string) error {
    x, err := strconv.Atoi(a)
    if err != nil {
        return err
    }
    y, err := strconv.Atoi(b)
    if err != nil {
        return err
    }
    fmt.Println("result:", x + y)
    return nil
}

Из десятка строк кода тела этой функции реальную работу выполняют только четыре (вызовы и последние две строки). Остальные шесть строк — это шум. Код слишком многословен, поэтому неудивительно, что жалобы на обработку ошибок уже годами находятся на вершине списков в опросах разработчиков. (Какое-то время жалобы на обработку ошибок обгоняла только досада из-за отсутствия дженериков, но теперь, когда Go поддерживает дженерики, обработка ошибок снова вернулась на первое место).

Команда разработчиков Go воспринимает отзывы сообщества со всей серьёзностью, поэтому мы много лет пытались придумать решение этой проблемы.

Первая явная попытка, предпринятая командой Go, произошла ещё в 2018 году, когда Расс Кокс формально описал проблему в рамках того, что мы тогда называли инициативой Go 2. Он изложил возможное решение, основанное на дизайне драфта Марселя ван Лохузена. Дизайн был основан на механизме check и handle и казался достаточно целостным. В драфт был включён подробный анализ альтернативных решений, в том числе сравнения с методиками, используемыми в других языках. Если у вас возникнет вопрос, рассматривалась ли раньше какая-то конкретная методика обработки ошибок, то прочитайте этот документ!

// реализация printSum, использующая предложенный механизм check/handle.
func printSum(a, b string) error {
    handle err { return err }
    x := check strconv.Atoi(a)
    y := check strconv.Atoi(b)
    fmt.Println("result:", x + y)
    return nil
}

Решение с check и handle расценили слишком сложным, и почти год спустя, в 2019 году, мы представили гораздо более простое и печально известное сегодня предложение try. Оно развивало идеи check и handle, но псевдоключевое слово check превратилось во встроенную функцию try, а от блока handle мы избавились. Чтобы изучить эффект встроенной функции try, мы написали простой инструмент (tryhard), переписывающий обработку ошибок так, чтобы она использовала try. Предложение активно обсуждали: оно набрало девятьсот комментариев в GitHub issue.

// реализация printSum с использованием предложенного механизма try.
func printSum(a, b string) error {
    // используем defer, чтобы повысить удобство ошибок перед возвратом
    x := try(strconv.Atoi(a))
    y := try(strconv.Atoi(b))
    fmt.Println("result:", x + y)
    return nil
}

Однако try влияла на поток управления, поскольку в случае ошибки выполняла возврат из внешней функции из выражений с потенциально глубокой вложенностью, что скрывало бы этот поток управления. Из-за этого наше предложение оказалось неприемлемым для многих разработчиков, и, несмотря на большие усилия, приложенные к разработке этого предложения, мы решили отказаться и от него. Оглядываясь назад, можно предположить, что лучше было бы ввести новое ключевое слово: это можно сделать сегодня, потому что теперь у нас есть гораздо более удобный контроль за версией языка благодаря файлам go.mod и директивам, применяемым к конкретным файлам. Ограничив использование try только присвоениями и операторами, мы бы, возможно, могли решить часть других проблем. Недавнее предложение Джимми Фрэша, которое, по сути, возвращается к исходному дизайну check и handle, устраняя некоторые его недостатки, движется в этом направлении.

Предложение try вызвало обширную реакцию и обсуждения, в том числе привело к публикации серии постов Расса Кокса «Thinking about the Go Proposal Process». В ней он пришёл к выводу, что мы снизили шансы на более удачный результат, представив почти полностью готовое предложение, почти не оставив места для отзывов сообщества и установив «пугающие» сроки внедрения. Цитата из «Go Proposal Process: Large Changes»: «сегодня можно сказать, что try было достаточно большим изменением […] которое должно было стать дизайном второго драфта, а не окончательным предложением с графиком внедрения». Но вне зависимости от возможных неудач в реализации процесса и коммуникаций, пользователи в целом были сильно против этого предложения.

В то время у нас не было более приемлемого решения, и мы много лет не занимались разработкой изменений синтаксиса обработки ошибок. Однако многих участников сообщества вдохновил наш пример, и образовался стабильный поток предложений с решениями проблемы обработки ошибок. Многие из них были очень похожи друг на друга, часть была интересной, часть непонятной, часть нереализуемой. Чтобы отслеживать расширяющуюся территорию изменений, ещё спустя год Иэн Лэнс Тейлор создал зонтичный issue со сводкой текущего состояния предложений по улучшению обработки ошибок. Для упорядочивания отзывов, обсуждений и статей по теме была создана Go Wiki. Независимо от нас другие люди начали собирать коллекции множества предложений по обработке ошибок, поступающих в течение всех этих лет. Их общий объём впечатляет: например, попробуйте изучить пост Шона Ляо «go error handling proposals».

Жалобы на отсутствие краткости при обработке ошибок продолжали поступать (см. Go Developer Survey 2024 H1 Results), поэтому после серии постепенно совершенствовавшихся внутренних предложений разработчиков Go Иэн Лэнс Тейлор опубликовал в 2024 году пост «reduce error handling boilerplate using ?». На этот раз идея заключалась в заимствовании конструкции, реализованной в Rust, а именно оператора ?. Расчёт был на то, что, положившись на уже готовый механизм с устоявшимся форматом и учтя наши уроки за много лет, мы наконец-то сможем добиться прогресса. В небольших неформальных исследованиях пользователей, в которых программистам показывали код на Go с использованием ?, подавляющее большинство участников правильно разобралось со смыслом кода, и это убедило нас сделать ещё одну попытку. Чтобы иметь возможность оценить влияние изменений, Иэн написал инструмент, преобразующий обычный код на Go в код, использующий новый синтаксис предложения, а также добавил прототип этой фичи в компилятор.

// реализация printSum с использованием предложенного оператора "?".
func printSum(a, b string) error {
    x := strconv.Atoi(a) ?
    y := strconv.Atoi(b) ?
    fmt.Println("result:", x + y)
    return nil
}

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

Спустя столько лет безуспешных попыток, накопив три полномасштабных предложения со стороны команды разработчиков Go и в буквальном смысле сотни (!) предложений сообщества, большинство из которых оказалось вариациями на одну и ту же тему, ни одно из них не завоевало достаточной (не говоря уже о подавляющей) поддержки. Возник вопрос: как нам двигаться дальше? И нужно ли двигаться вообще?

Мы думаем, что нет.

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

Цель процесса согласования предложений заключается в обеспечении общего консенсуса о результате в приемлемых временных рамках. Если при проверке предложений не удаётся прийти к общему консенсусу в обсуждении issue в issue tracker, то обычно результатом становится отклонение предложения.

Кроме того:

Может оказаться так, что при проверке предложения общего консенсуса добиться не удаётся, но тем не менее очевидно, что предложение не должно быть полностью отклонено. […] Если группа проверки предложения не может ни прийти к консенсусу, ни к следующему этапу предложения, то решение о дальнейшем пути возлагается на архитекторов Go […], которые изучают обсуждение и стремятся прийти к консенсусу в своём кругу.

Ни одному из предложений по обработке ошибок не удалось достичь ничего похожего на консенсус, поэтому все они были отклонены. Даже самые опытные члены команды Go в Google не смогли на текущий момент единогласно выбрать наилучший путь (возможно, ситуация когда-нибудь изменится). Но без подавляющего консенсуса мы не можем двигаться вперёд.

Вот весомые аргументы в пользу сохранения статуса-кво:

  • Если бы в Go синтаксический сахар обработки ошибок возник на ранних этапах развития, то немногие бы оспаривали его сегодня. Но прошло уже 15 лет, возможность упущена; к тому же, в Go есть вполне удобный способ обработки ошибок, пусть он иногда и кажется слишком длинным.

  • Рассмотрим это под другим углом: допустим, мы пришли сегодня к идеальному решению. Если внедрить его в язык, то мы получим вместо одной недовольной группы пользователей (голосующих за изменения) другую (предпочитающую статус-кво). Мы находились в схожей ситуации, когда решили добавить в язык дженерики, однако здесь есть важное отличие: сегодня никого не заставляют использовать дженерики, а хорошие дженерик-библиотеки написаны так, что пользователи благодаря выводу типов по большей части могут закрыть глаза на то, что это дженерики. Если же в язык добавят новую синтаксическую конструкцию для обработки ошибок, то практически всем придётся использовать её, чтобы поддерживать идиоматичность кода.

  • Предотвращение появления дополнительного синтаксиса соответствует одному из правил дизайна Go: не создавать нескольких способов реализации одной и той же функциональности. Из этого правила есть исключения в активно используемых областях, например, в присвоениях. Забавно, что возможность повторного объявления переменных в коротких объявлениях переменных (:=) была добавлена для решения проблемы, возникшей из-за обработки ошибок: без повторных объявлений цепочки проверок на ошибки требуют переменной err с разным для каждой проверки именем (или добавления объявлений отдельных переменных). В то время, вероятно, лучше было бы добавить больше синтаксической поддержки обработки ошибок. Тогда правило повторного объявления могло и не понадобиться, а благодаря его отсутствию и не возникли бы различные связанные с ним осложнения.

  • Если изучить код обработки ошибок, то можно заметить, что многословность становится не так важна, если ошибки действительно обрабатываются. Для хорошей обработки ошибок часто требуется добавление к ошибке дополнительной информации. Например, в опросах пользователи часто оставляют комментарии о нехватке связанных с ошибками трассировок стеков. Эту проблему можно решить при помощи вспомогательных функций, создающих и возвращающих расширенную ошибку. В этом примере (гипотетическом) относительный объём бойлерплейта намного меньше:

    func printSum(a, b string) error {
        x, err := strconv.Atoi(a)
        if err != nil {
            return fmt.Errorf("invalid integer: %q", a)
        }
        y, err := strconv.Atoi(b)
        if err != nil {
            return fmt.Errorf("invalid integer: %q", b)
        }
        fmt.Println("result:", x + y)
        return nil
    }
  • Новая функциональность стандартной библиотеки также может снизить объём бойлерплейта обработки ошибок в духе поста Роба Пайка «Errors are values». Например, в некоторых случаях для одновременной обработки серии ошибок можно использовать cmp.Or:

    func printSum(a, b string) error {
        x, err1 := strconv.Atoi(a)
        y, err2 := strconv.Atoi(b)
        if err := cmp.Or(err1, err2); err != nil {
            return err
        }
        fmt.Println("result:", x+y)
        return nil
    }
  • Написание, чтение и отладка кода — это достаточно различающиеся действия. Написание повторяющихся проверок на ошибки может казаться скучным занятием, но современные IDE имеют мощные функции автозавершения кода, некоторые даже с поддержкой LLM. Таким инструментам очень легко писать базовые проверки на ошибки. Многословность при чтении кода по большей части проста в понимании, но и в этом могут помочь инструменты. Например, IDE с настройками языка Go может иметь переключатель для сокрытия кода обработки ошибок. Такие переключатели уже существуют для других блоков кода, например, для тел функций.

  • При отладке кода обработки ошибок полезна возможность быстрого добавления println или отдельной строки для установки контрольной точки в отладчике. Это легко реализовать, если уже существует отдельный оператор if. Но если вся логика обработки ошибок скрыта за checktry или ?, то может потребоваться преобразование всего кода в обычную конструкцию if, что усложняет отладку и может даже привести к появлению незаметных багов.

  • Есть и соображения практичности: придумать новую идею синтаксиса для обработки ошибок легко, отсюда и берётся множество предпочтений со стороны сообщества разработчиков. Сложнее придумать хорошее решение, которое выдержало бы тщательный анализ. Для правильного проектирования изменений в языке и для их реализации требуются согласованные усилия. Но позже всё равно придётся платить за это высокую цену: весь код нужно будет переписать, обновить документацию и внести изменения в инструменты. С учётом всего этого, изменения в языке — очень дорогостоящий процесс, а команда разработчиков Go относительно мала и у неё есть много других приоритетных задач. (Но здесь всё может поменяться: приоритеты становятся другими, размеры команд уменьшаются и увеличиваются.)

  • Кроме того, некоторые из нас недавно получили возможность присутствовать на Google Cloud Next 2025, где у команды Go был свой павильон и где мы провели небольшой Go Meetup. Все пользователи Go, с которыми нам удалось поговорить, уверенно заявили, что нам не следует менять язык ради улучшения обработки ошибок. Многие сообщали, что отсутствие поддержки обработки ошибок в Go наиболее очевидна, когда переходишь на него с другого языка, где такая поддержка есть. Когда больше осваиваешься и начинаешь писать более идиоматичный код на Go, эта проблема становится гораздо менее важной. Разумеется, выборка была недостаточно репрезентативной, но множество людей на GitHub и их отзывы тоже можно считать ещё одним примером данных.

Разумеется, в пользу изменений тоже есть весомые аргументы:

  • Отсутствие качественной поддержки обработки ошибок остаётся основной причиной жалоб в опросах пользователей. Если команда Go действительно воспринимает отзывы пользователей серьёзно, то рано или поздно нам придётся с этим что-то делать. (Хотя подавляющей поддержки изменений в языке всё равно не наблюдается).

  • Наверно, неправильно было бы стремиться исключительно к снижению количества символов. Было бы лучше сделать стандартную обработку ошибок хорошо заметной при помощи ключевого слова, и при этом всё равно избавиться от бойлерплейта (err != nil). Такой подход может упростить чтение кода (для ревьюера!) и понимание того, какая ошибка обрабатывается, без необходимости дополнительной проверки, что повысило бы качество и безопасность кода. Это бы привело нас к самому началу, то есть к check и handle.

  • Мы не знаем, насколько на самом деле проблема синтаксической многословности проверки ошибок важнее, чем многословность хорошей обработки ошибок: конструирование ошибок — полезная часть API, важная и для разработчиков, и для конечных пользователей. Этот аспект нам бы хотелось изучить глубже.

Итак, пока ни одна попытка решения проблемы обработки ошибок не получила достаточной популярности. Если взглянуть на текущую ситуацию объективно, то нужно признать, что у нас нет ни всеобщего понимания проблемы, ни согласия о том, существует ли вообще эта проблема. Учитывая всё это, мы приняли следующее прагматичное решение:

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

Сообщество разработчиков приложило огромные усилия к исследованию и обсуждению этих вопросов. Пусть они и не привели к каким-то осязаемым изменениям в синтаксисе обработки ошибок, этот труд позволил нам улучшить многие другие аспекты языка Go и процессов. Возможно, в будущем у нас возникнет более чёткая картина относительно обработки ошибок. А пока мы собираемся сосредоточиться на развитии новых возможностей, улучшающих Go для всех и каждого.

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


  1. NeoCode
    04.06.2025 11:56

    Из-за вот этого правила

    не создавать нескольких способов реализации одной и той же функциональности.

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

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


    1. Dhwtj
      04.06.2025 11:56

      но если нельзя создать несколько частных способов реализации то это не поможет

      значит, правило вредное


      1. NeoCode
        04.06.2025 11:56

        В том то и дело, что несколько не связанных между собой частных не нужно, нужен один общий, но такой в котором все другие (в том числе и существующая явная обработка) окажутся именно частными случаями.

        Например, что мне нравится в Go так это единый синтаксис для обычных функций и лямбд. Во всех других языках изощряются со всякими стрелками и прочими спецсимволами, а в Go и там и там func - просто для лямбд не указывается имя. То есть единый синтаксис и внутри два частных случая - именованные функции и анонимные.


  1. rsashka
    04.06.2025 11:56

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

    А просто сделать классические исключения не позволяет сделать религия языка?


    1. Dhwtj
      04.06.2025 11:56

      это плохо

      хотя бы тем что

      • создает неявный поток управления

      • большие затраты на разматывание стека


      1. rsashka
        04.06.2025 11:56

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

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


        1. Dhwtj
          04.06.2025 11:56

          Не явный

          Ты никак не узнаешь, кидает функция исключения или нет. Если кидает то она не чистая и хрен что получится при использовании pipe

          let a = b.c().d()


          1. Tishka17
            04.06.2025 11:56

            Ну допустим мы придумали простой способ помечать, что она кидает исключение. Проблема решена? Или все таки вы настаиваете что для пробросе ошибки наверх (что происходит чаще чем обработка) обязательно нужен бойлерплейт


            1. Dhwtj
              04.06.2025 11:56

              Ответил чуть раньше. Пожалуй, основная проблема что исключение прервёт обработку остальной части коллекции


          1. Quintanar
            04.06.2025 11:56

            А явные ошибки чем лучше? Точно так же любая функция может вернуть непонятно что, непонятно при каких условиях. Мы с легкостью можем свести исключения к ошибкам - везде добавить catch, а обратное не получится.


      1. NeoCode
        04.06.2025 11:56

        А существующий механизм panic-recover это разве не оно?


        1. Slach
          04.06.2025 11:56

          нет вообще не оно... хотя и выглядит походим местами
          https://www.perplexity.ai/search/pochemu-panic-i-recover-v-gola-6lMaybMLSNeZvPJ2_cNm2Q


      1. yrub
        04.06.2025 11:56

        да-да, конечно плохо, еще дженерики это тоже было плохо и много чего другого, чего нет в go))

        • поток управления на 100% явный

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

        насчет чистоты: c этим есть споры, все же исключение это про control flow а не про состояние. то что вы написали это не pipe а цепочка вызовов, все понятно что будет и в стектрейсе все будет написано. Даже в java там где реальный пайп и лямбды (я про стримы) - все равно все понятно. Не понятно только когда у вас асинхронно и в разных потоках вызывается, но и на этот случай есть решение через инструментирование кода, но это уже мы о несколько другом. 

        Мораль басни проста: надо интересоваться тем, что есть за пределами вашего стека и относиться критически к любым "это не баг, а фича" или "зато все явно и понятно".


        1. Dhwtj
          04.06.2025 11:56

          Плохо хотя бы тем, что исключение внутри pipe для коллекции или потока остановит обработку всей коллекции, а если функциональная обработка ошибок, то не будет обработан только сбойный элемент

          Мораль басни проста: надо интересоваться тем, что есть за пределами вашего стека и относиться критически к любым "это не баг, а фича" или "зато все явно и понятно".

          Может, вам и начать?

          var numbers = lines                       // IEnumerable<string>
              .Select(int.Parse)                    // FormatException → итерация рвётся
              .Select(n => n * 2);                  // уже не выполнится
          IEnumerable<int> ParseSafe(IEnumerable<string> src)
          {
              foreach (var s in src)
                  try   { yield return int.Parse(s) * 2; }
                  catch (FormatException e)
                  { Log.Warn($"skip {s}: {e.Message}"); }   // 4 служебные строки на шаг
          }

          Вместо этого уродства можно писать

          var good = lines
              .Select(s => Try(int.Parse, s))    // string -> Result<int>
              .Select(r => r.Map(n => n * 2))    // Result<int>
              .Partition();                      // (IEnumerable<int> ok, IEnumerable<Ex> err)


          1. yrub
            04.06.2025 11:56

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


            1. Dhwtj
              04.06.2025 11:56

              Я не думаю о ней как о транзакции потому что это не транзакция


        1. bogolt
          04.06.2025 11:56

          затраты есть, но на то это и называется "исключение", они не предназначены для того, чтобы вы кидали их миллионами и просто так.

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


  1. johngetman
    04.06.2025 11:56

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


    1. 143672
      04.06.2025 11:56

      borgo довольно приятно выглядит


      1. Dhwtj
        04.06.2025 11:56

        The Borgo language adds to Go algebraic data types, pattern matching, Option.

        На этом всё


    1. VanKrock
      04.06.2025 11:56

      и получим v


  1. Dhwtj
    04.06.2025 11:56

    хотели простоты языка?

    страдайте теперь!


  1. Siemargl
    04.06.2025 11:56

    Давно не читал такого длинного "А не пошли бы вы все..."


  1. AuToMaton
    04.06.2025 11:56

    Программисты на Go уже давно и долго жалуются на слишком многословную обработку ошибок.

    Вот тут следовало бы посмотреть - какие именно программисты. Все подряд? Начинающие, которые постепенно перестают? Опытные, которые доходят до сложных проектов и тогда начинают жаловаться? Это три большие разницы…

    Если бы в Go синтаксический сахар обработки ошибок возник на ранних этапах развития, то немногие бы оспаривали его сегодня. Но прошло уже 15 лет, возможность упущена; к тому же, в Go есть вполне удобный способ обработки ошибок, пусть он иногда и кажется слишком длинным.

    Интересно, какой такой сахар. Вся статья доказывает - при принятых принципах построения языка его быть не может. Реверанс в пользу кого-то, он же политес?

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

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

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

    Если не этим ограничиться, то на этом можно было остановиться. Как по мне - вопрос снят.

    Моё личное впечатление - Go подталкивает к тому, чтобы писать функции, в которых не может возникнуть ошибок. Типа ой, strconv.Atoi(a) может не сработать, спасите помогите ошибок обрабатывать. А что это за a? Может это пользователь ввёл? Тогда надо было на всякий случай супротив кульных хацкеров санитарный контроль, а то и вовремя вежливо попросить передумать.

    А может, если не сработало, то может можно дефолт или предыдущее значение взять. А может нужно проверить - а не прописью ли число…

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

    Это замечательное отношение к работе. Не ожидал, что в тени Гугол и при выраженной популярности языка такое возможно. Было бы так с Julia или Scheme или Lua - ну допустим, но с Go - это респект. Королева Я в восхищении.

    Что интересно, оригинал - пост в блоге, почему-то именуемый статьёй, без возможности оставить комментарий. Так что моё желание своё восхищение выразить - увы.

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


    1. slonopotamus
      04.06.2025 11:56

      Это похоже на критику устройств Эппл - их больше всего критикуют те, у кого их нет.

      Дык может у них потому и нет устройств Эппл.

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


    1. Dhwtj
      04.06.2025 11:56

      С try удобно обрабатывать исчерпание стека или памяти, сигнал со стороны ОС, может быть арифметические проблемы, команду на останов горутины в конце концов

      Удобно, да. Но это небольшой набор случаев. Да и ничего страшного в функциональном стиле обработки таких ошибок.


    1. klanev
      04.06.2025 11:56

      "...или прошлое значение..."

      Ну это прям "Счастливой отладки"!


  1. VanKrock
    04.06.2025 11:56

    В vlang сделали так:

    fn print_sum(a string, b string) ! {
        x := strconv.atoi(a) or { return err }
        y := strconv.atoi(b) or { return err }
        println('result: ${x+y}')
    }
        
    fn main() {
        print_sum('123', '456') or { println(err)}
    }
    

    В принципе удобно


    1. Octagon77
      04.06.2025 11:56

      Может не "сделали", а "делают", согласно правилам русского языка применительно к бете?

      Дальше страницы сайта vlang не смотрел, так что спрошу - компилятор даёт возможность or опустить? Если нет - в принципе пойдёт.

      Но я бы со своими вкусами предпочёл, чтобы Go такое забраковал - хорошо когда or это or и ничего больше. И хорошо когда язык не навязывает понятие ошибки, в Go ошибка - просто значение, решение как делать было (типа) принято не языком, а авторами функций которые могут возвращать ошибку, так гибче. И как тут быть в стиле Олега Тинькова - сомнительно, но ОК? А в Go - такое элементарно.


      1. VanKrock
        04.06.2025 11:56

        "делают" это если бы они сказали будет так, мы уже делаем, а тут именно это уже готово. То что бета - означает, что есть ошибки и некоторые конструкции могут измениться в будущем. Конструкцию or опустить нельзя (на самом деле вроде если постараться то можно, например если переменная может принять ошибку как валидный результат например !int, но это тоже нужно указать явно, после вызова метода поставить !) or в vlang это обработка ошибки и ничего более, это не || ну а то, что ошибка имеет стандартный интерфейс - это тоже хорошо, никто не мешает возвращать несколько значений как в go

        fn get() (int, int) {
           return 1, 2
        }
        


    1. Dhwtj
      04.06.2025 11:56

      многословно

      тогда уж

      fn print_sum(a string, b string) ! {
          x := strconv.atoi(a) or err
          y := strconv.atoi(b) or err
          println('result: ${x+y}')
      }
          
      fn main() {
          print_sum('123', '456') or { println(err)}
      }

      если последняя переменная по умолчанию возвращается как в раст


      1. VanKrock
        04.06.2025 11:56

        если написать x := strconv.atoi(a) or {err} то он будет пытаться присвоить x := err и компилятор выдаст ошибку так как нельзя присвоить IError в int
        зато можно писать так x := strconv.atoi(a) or { 0 }


        1. Dhwtj
          04.06.2025 11:56

          Надо возвращать result < T, Err>

          Как в нормальных языках


          1. VanKrock
            04.06.2025 11:56

            Можно кстати вообще так

            fn print_sum(a string, b string) ! {
                x := strconv.atoi(a)!
                y := strconv.atoi(b)!
                println('result: ${x+y}')
            }
            
            fn main() {
                print_sum('a', '456') or { println(err)}
            }
            


            1. Dhwtj
              04.06.2025 11:56

              Функция без указания типа?

              Подразумевается void?

              А если задуматься, то и не void совсем, а ХЗ что.

              Не, я в такие игры не играю

              Result<(), Error>) видимо

              Лучше это отображать явно


  1. Cfyz
    04.06.2025 11:56

    В интернетах периодически можно видеть обсуждения а не добавить ли в C механизм defer? И там тоже половина воспринимает идею в штыки, мол не надо в наш простой и понятный C тащить всякие неявные defer, есть же goto.

    И вот представляется мне как разработчики на Go смотрят на это и думают, вот чудные, такой простой, понятный и удобный механизм не хотят использовать. А потом переключаются на соседнюю вкладку и критикуют синтаксический сахар обработки ошибкок, мол не надо в наш простой и понятный Go тащить всякие неявные try, есть же if =).

    На это смотрят разработчики например на Rust и думают, вот чудные, такой простой, понятный и удобный механизм не хотят использовать. А потом переключаются на соседнюю вкладку и...


    1. Dhwtj
      04.06.2025 11:56

      И критикуют Higher-kinded types

      А может завидуют


    1. Sulerad
      04.06.2025 11:56

      Мне кажется, тут есть большое отличие в подходах к языку. Если брать Си и Го, то в их дизайне очень большое влияние имеет простота и минимализм, а любой сахар и новые фичи воспринимаются если не в штыки, то с большим сомнением.

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

      Как пример — в го несколько разных хороших и проработанных вариантов обработки ошибок оказались отброшены в основном потому что в комьюнити не нашли всеобщей поддержки. А вот в том же расте есть let-else statement, в обсуждении которого под 200 комментариев и который полностью совпадает по функционалу с имеющейся конструкцией match или if, но имеет более упоротый узкоспециализированный синтаксис. И ничего, пободались, приняли, довели до stable.


  1. bogolt
    04.06.2025 11:56

        x, err1 := strconv.Atoi(a)
        y, err2 := strconv.Atoi(b)
        if err := cmp.Or(err1, err2); err != nil {
            return err
        }

    вот этот пример максимально нердужелюбен к код ревью. Потому в дифе будет например\

    x, err1 := strconv.Atoi(a)
    y, err2 := strconv.Atoi(b)

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


  1. checkpoint
    04.06.2025 11:56

    А можно я попробую. Чисто дедовскими методами, без всяких try/catch и прочих непонятных операторов, без скрытых переменных и лишних скобок захламляющих код. Почти Python. ;)

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    extern int errno;
    
    int main(int argc, char *argv[]) {
    	long a, b, c;
    
    	if(argc < 4)
    		return 255,
    		printf("Usage: %s <long> <long> <long>\n", argv[0]);
    
    	a = strtol(argv[1], NULL, 0);
    	b = strtol(argv[2], NULL, 0);
    	c = strtol(argv[3], NULL, 0);
    
    	if(errno)
    		return errno,
    		printf("Error: %s\n", strerror(errno)); 
    
    	printf("Result: %ld\n", a + b + c);
    
    	return 0;
    }
    
    rz@butterfly:~ % cc -Wno-unused-value a.c
    
    rz@butterfly:~ % ./a.out 1 2
    Usage: ./a.out <long> <long> <long>
    
    rz@butterfly:~ % ./a.out 1 2 3
    Result: 6
    
    rz@butterfly:~ % ./a.out 1 2 -4
    Result: -1
    
    rz@butterfly:~ % ./a.out 1 2 0xffff
    Result: 65538
      
    rz@butterfly:~ % ./a.out 1 2 fsck
    Error: Invalid argument
    
    rz@butterfly:~ % ./a.out fsck 2 3
    Error: Invalid argument
    
    rz@butterfly:~ % ./a.out 1 fsck 0
    Error: Invalid argument
    
    rz@butterfly:~ % ./a.out 1 3 444444444444444444444444444444444444444444444444
    Error: Result too large


    1. Panzerschrek
      04.06.2025 11:56

      Очень плохой код. Забыть проверить значение errno - проще простого. Передать дополнительную информацию о причине ошибки тоже не возможно, есть только целочисленный код ощибки, в который подробностей не впихнуть.


      1. checkpoint
        04.06.2025 11:56

        Всё тоже самое можно отнести и к try/catch, и к описанному в статье решению.

        Достоинство дедовского метода состоит в том, что он не требует никакого синтаксического сахара, а код локаничен и понятен. В таком включе можно писать на любом языке, хоть на макроассемблере. Изобретатели же новых языков находятся в постоянном поиске, они придумывают конструкции которые только усложняют жизнь.

        есть только целочисленный код ощибки, в который подробностей не впихнуть.

        А зачем Вам подробности ? В подавляющем случае достаточно просто знать была ошибка при выполнении какого-то промежуточного шага программы или нет. Код в errno вполне самодостаточен достаточен.


        1. Panzerschrek
          04.06.2025 11:56

          придумывают конструкции которые только усложняют жизнь

          В Rust нельзя игнорировать Result типы - компилятор породит ошибку. А чтобы не игнорировать и просто пробросить ошибку, существует оператор ?. Это отдельное синтаксическое нововведение, но оно упрощает жизнь, делая написание некорректного кода очень сложным и вынуждая писать правильно.

          Код в errno вполне самодостаточен достаточен.

          Нет. Например, если файл не сумели открыть, полезно сохранить имя файла. Или если сетевое соединение не прошло, кроме кода что-то ещё может быть полезно. В Rust существует даже библиотека, которая предназначена для добавления контекста в ошибки.


        1. klanev
          04.06.2025 11:56

          Только при наличии потоков (и вообще параллельщины) errno - просто антипаттерн


          1. checkpoint
            04.06.2025 11:56

            Откуда Вы это взяли ? errno is thread safe очень давно. Никаких проблем с нитями у errno нет.


            1. Panzerschrek
              04.06.2025 11:56

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


              1. checkpoint
                04.06.2025 11:56

                Кто мешает в yeild() сделать сохранение errno на ряду с другими параметрами контекста ? В общем-то это уже давно сделано за вас.


    1. Dhwtj
      04.06.2025 11:56

      почти идентично и с теми же недостатками

      https://habr.com/ru/articles/915468/comments/#comment_28395806


  1. Panzerschrek
    04.06.2025 11:56

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

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


  1. WayMax
    04.06.2025 11:56

    не создавать нескольких способов реализации одной и той же функциональности.

    Замечательная задумка. А как на счет ее реализации? Например сгенерировать страничку с текстом "Hello World!"? Это же просто и наверняка существует только один способ сделать это?

    func route1(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World!"))
    }

    func route2(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "Hello World!")
    }

    func route3(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World!")
    }


    1. Dhwtj
      04.06.2025 11:56

      Это вопрос к библиотекам, не?


      1. WayMax
        04.06.2025 11:56

        "Не". Здесь не используются библиотеки. Только стандартные возможности языка.


        1. olivera507224
          04.06.2025 11:56

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

          import (
            "http"
            "io"
            "fmt"
          )


        1. Dhwtj
          04.06.2025 11:56

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