В другой раз, модераторы StackOverflow отклонили, закрыли, нарисовали и четвертовали еще один вопрос по непонятным причинам. Итак, я перенесу сюда свой ответ с некоторыми подробностями.

Вопрос был в том, «почему создатели Go решили не рассматривать nil как ложь?». Я думаю, что это отличный вопрос, и он может улучшить понимание некоторых концепций при изучении.

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

int a = 5;
if (a) {
  printf("a is non-zero!");
}

Это напечатает «a не равно нулю!». Синтаксис сделал язык проще и легче. Логические значения можно обойти, используя целые числа и макросы. Усложнять язык не пришлось. Элегантный.

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

char *a = "test";
if (a) {
  printf("a is a valid pointer!");
}

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

Как и у любого ярлыка, у него был свой набор проблем. Во-первых, непонятно, что означает выражение !a. Проверяем ли мы действительность указателя, проверяем ли мы значение логического или просто проверяем, равно ли целое число нулю?

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

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

a bool = true
b *bool = make(bool, false) // if Go supported this
a = b                       // should have been "a = *b"

Строка a = b не вызовет проблем с компилятором, а значение a все равно будет true. Это вызовет ошибки и проблемы с читабельностью.

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

a int = 3
b *bool = make(bool, false) // let's keep imagining
if a == 3 && b {
  println("Problem!");
}

В приведенном выше коде неправильно будет выведено «проблема». Выражение должно было быть a == 3 && *b, еще одна пропущенная ошибка.

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

Зачем людям это нужно?

Go полагается на код явной обработки ошибок, а не исключений. Чтобы проверить, успешно ли выполнялась функция, вы должны проверить, является ли возвращаемое значение ошибки нулевым или нет, и, соответственно, вернуться раньше. Итак, вам нужно написать что-то вроде:

result, err := someFunc()
if err != nil {
  return err
}

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

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

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

Если бы в Go были обобщенные типы и размеченные объединения, можно было бы разработать несколько элегантных механизмов обработки ошибок. Без них мы будем придерживаться нынешнего образа жизни по уважительным причинам. И да, мы должны явно писать a != nil каждый раз, потому что это делает наш код лучше.