Субботним утречком решил поговорить о кое-чем действительно важном. Управление памятью, сборщик мусора — это всё недостойная обсуждения фигня. Имена переменных — вот это действительно стоящая тема. Не вижу, почему бы трем благородным донам её не обсудить.
Для тех, кто пишет на Go давно, изложенное ниже может показаться очевидным, но буду рад вашим комментам (панамку за некоторую сумбурность изложения приготовил).
Одной из ключевых особенностей Go является ориентация на читаемость и краткость кода. Это проявляется как в конструкциях языка, так и в стилевых рекомендациях, принятых сообществом и разработчиками языка.
Короткие имена переменных — важная часть этой философии. В отличие от языков, где длинные и описательные имена переменных могут быть нормой (например, PHP или Java), Go поощряет использование коротких имен, особенно в случаях, когда их смысл легко понять из контекста.
Роб Пайк (надеюсь, не надо представлять этого благородного дона) в одном из старых своих постов писал…
Ah, variable names. Length is not a virtue in a name; clarity of expression is.
Эндрю Джеррард (он чуть менее известен, так что вот ссылка на его гитхаб) посвятил в 2014-м году этой теме один из первых своих докладов по Go, который буквально так и называется: What's in a name
Эти идеи прослеживаются в стандартной библиотеке и Go’s Code Review comments, где прямо сказано:
вместо lineCount лучше использовать просто c
Я немножко забежал вперед, просто сразу хотелось сослаться на авторитеты. Школьная учительница по риторике всегда говорила, что ссылка на авторитеты в начале - это хорошо и надежно (спасибо вам, Марианна Юрьевна)
func countLines() int {
// do stuff
var linesCount int
for i := 0; i < lines; i++ {
linesCount += 1
}
return linesCount
}
// Не читается лучше чем
func countLines() int {
// do stuff
var c int
for i := 0; i < lines; i++ {
c += 1
}
return c
}
Почему так?
В обоих случаях переменные i и c/linesCount являются кратковременными. Их область видимости очень мала, блок кода, где они используются, тоже небольшой. Вы сразу видите, где они заканчиваются, и вам не нужно прокручивать слишком много.
Верхняя версия содержит больше текста, но смысл не стал понятнее. Напротив, дополнительная длина мешает восприятию.
В стандартной библиотеке Go короткие имена используются повсеместно. Например, функция io.Copy:
func Copy(dst Writer, src Reader) (written int64, err error) {
var buf [32 * 1024]byte
for {
n, err := src.Read(buf[:])
if n > 0 {
nw, ew := dst.Write(buf[0:n])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if n != nw {
err = io.ErrShortWrite
break
}
}
if err != nil {
if err == io.EOF {
err = nil
}
break
}
}
return written, err
}
Здесь используются короткие имена: dst (destination), src (source), n (number of bytes). Они интуитивно понятны, потому что:
Их смысл ясен из контекста функции.
Область видимости переменных ограничена циклом или функцией.
Или в пакете math
func Max(x, y float64) float64 {
if x > y {
return x
}
return y
}
Имена x и y передают математическую сущность, а не логику программы. Более длинные имена, такие как firstNumber и secondNumber, только усложнили бы восприятие.
На самом деле любой файл из src можно в качестве примера приводить, так что не буду эту часть растягивать
Когда оправдано использовать короткие имена переменных?
Если вкратце - когда они помогают снизить визуальный шум и улучшить читаемость за счет лаконичности:
Когда переменная используется в небольшой функции
В общепринятых сокращениях, которые широко используются и понятны большинству разработчиков: err для ошибок, ctx для контекста, i, j для индексов в циклах, ok для булевых вовращаемых параметров функции и т.п.
Для временных переменных, которые используются в одном месте или в короткой области (1-7 строк) часто достаточной однобуквенной перменной, а для средней области видимости (7-15 строк) короткого слова
Для ресивера метода почти никогда не нужно больше двух символов, лучше один
-
Для распространенных типов: r для io.Reader или *http.Request, w для io.Writer или http.ResponseWriter и т.п.
Стоит помнить, что имя локальной переменной должно отражать её содержимое и то, как она используется в текущем контексте, а не то, откуда произошло значение.
Тем не менее
Имя переменной, которое может быть совершенно понятным (например, c для счетчика) в пределах небольшой области видимости, может оказаться недостаточным в более широкой области. В этом случае потребуется более ясное название, чтобы напомнить читателю о назначении переменной в дальнейших частях кода.
Простое правило (примерно в таком виде оно изложено в Go code review comments)
чем дальше от места объявления используется переменная, тем подробнее должно быть имя
Короткие имена создают путаницу, если используются в глобальных переменных или экспортируемых идентификаторах.
var cfg string // непонятно, что это за конфигурация
var databaseConfig string // так понятнее
Если переменная хранит сложную структуру, которая используется во многих местах, длинные имена помогают понять её назначение.
type User struct {
ID int
FirstName string
LastName string
}
Имена FirstName и LastName однозначно описывают содержимое переменной, в отличие от кратких Fn и Ln.
Если смысл переменной не ясен из контекста, лучше использовать описательное имя.
func CalculateDiscount(price float64, percentage float64) float64 {
discountAmount := price * percentage / 100
return price - discountAmount
}
В конечном счете, цель использования коротких имен переменных заключается в том, чтобы сконцентрироваться на функциональности кода, избегая лишней детализации там, где она не нужна. Это делает Go тем языком, который мы любим за его простоту и выразительность.
Помните:
Лаконичность кода — это не цель, а средство для достижения читаемости и понимания.
P.S. Я немого устал под конец статьи и пожалуй, опубликую ее в таком виде, чтобы она не ушла в страну недописанных черновиков. Нужность статьи была продиктована обсуждениями, с которыми иногда я (полагаю не я один) сталкиваюсь на ревью. Всем хорошего дня =)
Комментарии (57)
alabamaa
11.01.2025 11:59Примерно, такую же концепцию имен использую и на C++, и по этому поводу даже были разногласия с партнерами по разработке. Благодаря вашей статье усилилось желание познакомиться с языком Go.
sl4mmer Автор
11.01.2025 11:59Ну Роб Пайк (которого я в начале упомянул), он свои идеи о стиле, ставшие основой философии Go вынес из своего С опыта, есть его статья, 1989-го года, "Notes on Programming in C" - вам возможно будет любопытно, там и про именование есть
cupraer
11.01.2025 11:59Основа философии Го была напрямую сформулирована Пайком: язык для тупых, но старательных. Вот прямая цитата:
They’re [Googlers] not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt.
Ещё Пайк не использует подсветку синтаксиса:
Syntax highlighting is juvenile. When I was a child, I was taught arithmetic using colored rods. I grew up and today I use monochromatic numerals.
Есть гипотеза, что для использование такой концепции наименования, надо бы и от IDE отказаться, и писать на чем-нибудь посерьезнее, типа си, хотя бы.
Mur466
11.01.2025 11:59Мне плевать на авторитеты. Тем более в цитате Пайка приветствуется ясность, а автор выдаёт ее за краткость.
Что такое nw и ew сам не догадался.
Го с его обработкой ошибок через err совсем не лаконичен, а наоборот.
Детализация может казаться излишней писателю, так как для него всё очевидно. Но никто не знает, во что этот код разрастется с годами, и кто его будет читать и поддерживать. И там станет всё совсем не так очевидно.
Стадартные сокращения типа err и ctx надо соблюдать, но даже в указанном примере вместо errWrite сделали ew и сразу стало непонятно. Если это стандарт в модуле, и встрчается многократно с этим можно жить. Но если сокращения бессистемные или неоднозначные, это ужас. Например переменная c может одначать и char и count и client и черта лысого.
Поэтому уважайте тех кто придёт за вами, и не жалейте буковок для названий переменных.
sl4mmer Автор
11.01.2025 11:59вопрос здравого смысла, если с используется только строкой-двумя ниже, то это подходящее имя, если строками пятью ниже и больше то уже есть смысл в cnt или count
>Поэтому уважайте тех кто придёт за вами
В том и смысл,краткость (там где она уместна) и нужна для ясности, чтоб читающему было удобнее, не нужно задерживаться взглядом на каждой yetAnotherVar,
>Детализация может казаться излишней писателю, так как для него всё очевидно
Детализация должна быть уместной и не нарушать лаконичность, излишняя детализация, там где она не нужна, ухудшит читаемость, мы же в первую очередь заботимся именно о читающем.
Я понимаю вашу позицию, и во многом вы правы, сам я до этого очень долго писал на php/java/delphi/python и привык считать говорящие имена хорошим тоном, но стоит сказать, что за последние годы скорректировал свою позицию в сторону более взвешенной - краткость, там где она к месту, прекрасна. А бездумным применением практик - можно любой подход дискредетировать
Касаемо обработки ошибок, ну мне уже трудно сказать - сначала подход казался непривычным и не очень удобным, сейчас после почти 10 лет на go, он для меня уже как родной, у него есть свои плюсы
tolyanski
11.01.2025 11:59Касаемо обработки ошибок, ну мне уже трудно сказать - сначала подход казался непривычным и не очень удобным, сейчас после почти 10 лет на go, он для меня уже как родной, у него есть свои плюсы
Соглашусь, same here. Правда в моем случае не 10 а 5 лет.
Но я считаю, что мы просто уже привыкли к этому. Человек ведь такая сволочь, ко всему привыкает)Но регулярное продолжение пописывания мной то на C++, то на C#, то на PHP, напоминает мне, от чего я отказался...
tolyanski
11.01.2025 11:59Одной из ключевых особенностей Go является ориентация на читаемость и краткость кода.
Каждый раз, когда кто-то где-то упоминает "ориентацию на читаемость в Go", я со слезами пересчитываю, сколько раз я за сегодня споткнулся об `if err != nil`, разбирая очередной спагетти...
Noah1
11.01.2025 11:59Как 'if err != nil' мешает читаемости? Предельно простая конструкция, ничуть хуже не делает
tolyanski
11.01.2025 11:59Так же как и обсуждаемая здесь захламляемость пространства длинными именами.
if err != nil через каждые 3-5 строчек, это ни что иное как нагромождение, мешающее визуально быстро распознавать полезную логику.В особых случаях несколько помогает IDE, у которой есть возможность "сворачивать" этих монстриков, чтобы вычитывать полезный код. Но даже сам факт, что GoLand от JetBrains сделал такую фичу под названием code folding для таких кейсов, говорит о том, что сам по себе код на Go - не идеально читаемый, если открыть его в простом блокноте. Помню похожую фичу в CLion для C++ - так она там только для автоматического сворачивания портянки импортов. А в Go прям по самому коду у каждого столба надо сворачивать мусор.
Разумеется, все познается в сравнении. Я говорю про это потому что повидал достаточно других языков, где с этим делом проблем поменьше. Да, разумеется, у других языков своих собственных проблем хватает. Но я и не утверждаю, что язык X объективно лучше языка Y.
sl4mmer Автор
11.01.2025 11:59я выше уже писал, мне чтоб полюбить if err!=nil понадобилось наверное лет 5-6, использования го как основного языка и должен сказать у этого подхода есть определенные плюсы
Как минимум, не возникает сложных и скрытых механизмов обработки ошибок, магии которую легко создать эксепшенами. Вместо этого ошибки - обычные значения, которые можно проверять и обрабатывать явно. Поведение становиться более очевидным
С точки зрения читаемости if err != nil делает обработку ошибок явной и легко заметной. Ошибки не “заглушаются” и не остаются незамеченными (ну в идеале)
В целом такая обработка ошибок это фича дизайна языка - сознательное решение, направленное на упрощение кода, увеличение его читаемости и контроль предсказуемости.
tolyanski
11.01.2025 11:59В целом такая обработка ошибок это фича дизайна языка - сознательное решение, направленное на упрощение кода
Я вроде где-то читал, что это не фича дизайна, а именно что реализовать обработку ошибок по-другому (более привычному) способу сложно из-за ориентированности языка на многопоточность и ее оптимизацию и тд. Не помню точные аргументы, что именно пошло не так / не получилось, надо будет на досуге погуглить еще раз
sl4mmer Автор
11.01.2025 11:59ну и это тоже, да и момент с производительностью, исключения требуют дополнительной нагрузки на систему (например, из-за управления стеком).
h1pp0
11.01.2025 11:59чтоб полюбить if err!=nil понадобилось наверное лет 5-6
Значит ли это, что разработчик, похожий на вас, будет не любить самый популярный паттерн языка первые 4 года? Звучит не очень хорошо
Noah1
11.01.2025 11:59Но ведь это необходимое "зло". Как иначе могла бы выглядеть индивидуальная обработка ошибок? Максимум можно на одну строку сократить с вложенным if. Стандартные try catch - гораздо хуже.
Go добился подробной и предельно простой обработки ошибок при помощи стандартной конструкции, без спец синтаксиса, по-моему весьма элегантно.
tolyanski
11.01.2025 11:59В том и дело, что "индивидуальная" обработка ошибок не во всех случаях нужна, но при этом нужна гарантированная остановка выполнения сразу после появления ошибки.
Но то что это необходимо зло - полностью согласен.
LiveRock
11.01.2025 11:59Как уже заметили выше, в большинстве случаев в Go имеет место не «индивидуальная обработка» ошибок, а «выталкивание» их наверх — в вызывающий код. И в этом случае не помешал бы какой-нибудь синтаксический сахар, что-нибудь вроде:
func findPatternInFile(pattern string, filePath string) ([]string, error) { var err! error default return nil, err re, err! := regexp.Compile(pattern) // err != nil: default return file, err! := os.Open(filePath) // err != nil: default return result := make([]string, ...) ... return result, nil // successful return }
Vitimbo
Мимопроходящий шарпист негодует. В приведенном примере я не могу просто посмотреть на 9 строчку и понять что такое nw и с чем сравнивается. Надо возвращаться до объявления, смотреть что за метод вызывается, что он возвращает и только потом прыгать обратно на 9 строку, где есть еще какое-то n, которое описано еще выше. При написании с нуля, когда контекст и задача полностью в голове, это не составит проблем, но читать чужое, или свое, через какое-то время, может вызвать определенные сложности.
Согласиться можно только с Max и то, можно было бы заменить x и y на first и second. Если код будет побольше и не очень хорошо структурирован, то придется сидеть и расшифровывать, что же хотел сказать автор.
ИМХО, буквы ведь бесплатные, почему бы не воспользоваться ими? Ведь код не всегда будет короткий и грамотно разбит на функции.
skovpen
В современной версии пакета
n
переименовали вnr
и стало логичней и понятней :-)В шарпах тоже любят
FileStream fs = ...
Vitimbo
В шарпах за такое дважды должны ударить по рукам. 1 раз за FileStream вместо var, второй раз за само название. У меня на такое название даже ide заругается, что "тут опечатка, поправь, кожаный".
segment
Если уже говорить в таком ключе, то "бить по рукам" нужно как раз таки за использование var.
tolyanski
Почему? В C# var никакого негативного влияния на перформанс не имеет, и никакого отношения к т.н. динамическим типам тоже не имеет.
Это всего лишь полный аналог auto в C++ и := в Go. Реальный тип переменной определяется на этапе компиляции.
segment
Иногда довольно сложно понять с каким типом имеешь дело. Ну вот к пример, открыл я код в bitbucket/github/notepad, и как мне посмотреть глазами на тип данных? Не всегда же используется IDE, да и не всегда удобно наводить курсор тогда, когда можно было этого не делать. Сам набор текста, даже с явными типами, занимает сильно меньше времени чем отладка и сопровождение.
tolyanski
Аргумент. Но в то же время, когда пишете код, и нужно в новую переменную положить результат какой-то библиотечной фукнции, полную сигнатуру которой вы не помните, вам IDE подсказала только ее название и список аргументов, но возвращаемый тип у нее например std::map<time_t, const SomeType&> - такое вводить при каждом объявлении новой переменной (а сначала зайти в библиотечную функцию и скопировать этот тип) - довольно таки нудное занятие :)
Так или иначе, вроде бы стайлгайды разных языков, где есть возможность не указывать руками тип новой переменной, рекомендуют этого не делать.
А если создаете новый объект, то как бы и совсем смысл теряется два раза повторять тип:
segment
Если говорим о C++, то там действительно тип может иметь сложную сигнатуру и прописывать его будет нудно. Или там какие-нибудь итераторы, где в целом может быть понятен хранимый тип. Еще бывают случаи, когда программист задумывает возможность "безболезненной" смены типа данных где-то выше по коду и во всех местах auto останется без изменений, мне такой подход не нравится, но я могу согласится с некоторыми доводами в случае C++. Когда мы говорим о C#, то сейчас можно сместить тип в объявлении в левую сторону, оставив в правой только new(), таким образом код будет последователен если придерживаться отсутствия var.
tolyanski
аргумент)
Vitimbo
Открываем стайлгайды от майкрософт и видим рекомендацию всегда использовать var, кроме случаев, когда тип надо указать явно или компилятор не может вывести его из контекста. Также, все ide будут предлагать заменить явный тип на var.
Аргумент, что сложно понять тип не из ide мне не очень понятен, это не баш скрипты, чтобы их ковырять в блокноте. Оценить качество кода можно и без знания типов или вообще пользоваться инструментами для ревью в самой ide.
segment
Не совсем понял о какой такой рекомендации Вы говорите. Открываем что находится из последнего, и тут читаем:
И эти рекомендации выглядят нормально, речь в них в том, что можно (и даже не обязательно) использовать var, когда явно понятен тип из выражения.
Возможно, характер работы у Вас это позволяет. Мне же иногда нужно смотреть в историю разных репозиториев, держать код открытым не только в IDE (например, сохраненный участок кода во время рефакторинга). И так как кодовая база может быть довольно большой и старой, то не всегда помнишь контекст, поэтому во время поиска и решения проблем я хочу смотреть на код как на полноценный текст. Для меня явное лучше неявного, поэтому указание типа (а с codepilot это еще и быстрее) наоборот является хорошим тоном.
sl4mmer Автор
ну на самом деле, из контекста там понятно, что nw это сокращение он number of bytes written
Буквы безусловно бесплатные, но вся идея в том, что не должно быть лишнего визуального шума, усложняющего восприятие - безусловно тут нужен сбалансированный поход конечно, исходя главным образом из здравого смысла
>Ведь код не всегда будет короткий и грамотно разбит на функции
А вот к этому стотит стремиться - го располагает к этому, даже гранулярность обработки паник (читай эксепшенов) на уровне функций. Естественно если функция длинная и переменная используется в широком контексте (более 15 строк) то стоило бы например в примере из io. написать numWritten например
dopusteam
А почему не bw? Кажется логичнее было бы
А ew что значит?
sl4mmer Автор
>А почему не bw? Кажется логичнее было бы
Возможно, тут уже кому-как
>А ew что значит?
Ошибка записи, стандартное имя err не использовали, чтобы не затенять ошибку чтения, при успешной записи
dopusteam
Хороший комментарий, каждый придумывает своё сокращение, но 'из контекста понятно' всем.
Какие то костыли в десятке строк кода) и я про сокращение спрашивал, а не про смысл) ew - error of writing? Почему не we?)
sl4mmer Автор
С еw понятно - ошибка если уж не err, то с e начинается, хотя бы я назвал errw, например
tolyanski
Еще в таких случаях, чтобы не затенять переменную err, могут лепить переменные err2, err3 и тд )
sl4mmer Автор
цифры в именах переменных это мрак, конечно, очень давно такого не видал
Vitimbo
Понял это только после вашего уточнения, изначально читал 9 строку как "ЕСЛИ Н НЕ РАВЕН СевероЗапад, ТО ..."
Это не означает, что стоит уходить в крайности. Переменная типа countOfBytesReadedFromRecentOpenedFile (хотя, я видел и даже пару раз писал и по длиннее имена) также ужасно как и nw/nbf. В данной ситуации идеально подошло бы readedBytesCount. Лаконично и полностью отражает что тут лежит.
Стремиться, может быть, и стоит, только любой успешный продукт рано или поздно начинает разрастаться. Мне доводилось повидать проекты, превратившиеся в монстров. Уверен и на go такие уже существуют. И то, что функция сегодня складывает 2 числа не защитит ее от того, что завтра прилетит менеджер и попросит прикрутить туда логи и тонкую настройку доступов, чтобы для проверяющих 2 + 2 было 4, а для бухгалтера столько, сколько надо. И вот, ваша функция на 2 строки с переменными a b c мутировало во что-то, чего будут бояться даже чеченцы.
Итого, мы получаем какой-то набор аббревиатур, которые известны только их создателю, и то, пока он их не забыл. И то, у одного в голове логично будет смотреться nw, а у другого bc или wbc. Вы друг друга даже не поймете. Это примерно одинаковый уровень упоротости с параметрами утилиты tar в линуксе.
Жить намного проще, когда код можно читать как простое предложение на английском языке.
bogolt
Кхе-кхе, read это неправильный глагол, поэтому формы readed не существует. Примерно как по-русски кто-то бы попытался написать "победить" в будущем времени ( победю, побежду... ? )
Vitimbo
Как-то я упустил этот момент. Аббревиатура спасает от неправильной формы глагола.
sl4mmer Автор
>Это не означает, что стоит уходить в крайности.
Абсолютно согласен, в применении любого подхода, нужен в первую очередь здравый смысл
tolyanski
Вы серьезно про "не должно быть лишнего визуального шума", говоря о языке Go? смешно)
Noah1
В Go очень мало визуального шума. Удивляюсь что кто-то Go называет плохо читаемым.
sl4mmer Автор
любопытно, разовьете мысль?
tolyanski
Сравните два куска псевдокода ниже, которые делают одно и то же:
Noah1
Это подмена понятий, 2 примера кода делают НЕ одно и тоже. Во втором примере обрабатывается ошибка на каждый вызов функции, в первом - ошибки игнорируются.
В классическом языке с исключениями, обработка каждой ошибки была бы ещё хуже:
И не нужно писать про то, что в try можно обернуть все 3 функции - это уже не будет обработкой индивидуальных ошибок.
`if err != nil` не идеальное, но элегантное в своей простоте решение.
tolyanski
Зависит от ситуации. Есть случаи когда достаточно именно обработки общего исключения, прилетающего от верхней функции, независимо от того, насколько глубоко в цепочке вызовов она возникла. При этом важно чтобы при возникновении любой ошибки выполнения следующих функций не произошло. Такого на Go не сделать, надо именно что руками останавливать выполнение при любой ошибке.
Иными словами, у меня должна быть такая возможность, обрабатывать либо индивидуальные ошибки, либо общие, при этом чтобы гарантировалась остановка выполнения в любом случае.
Можно же на верхнем уровне сделать несколько catch с разными типами исключений. Можно еще много всего разного (было бы), зависит от конкретного случая
Lezvix
В Go решения принимали не наобум, исключения эта штука неоднозначная, ими часто злоупотребляют, по этому решили передавать ошибки явно, всегда проще обработать ошибку там, где она случилась. И многие современные языки придерживаются такого подхода: Rust, Zig
При этом действительно исключительные исключения, которые не позволяют продолжить исполнение без их обработки в Go вполне себе есть, и реализуются через panic и recover
dopusteam
В примерах из статьи вся обработка заключается в том, чтоб вернуть ошибку в вызывающий метод
Lezvix
Мне кажется пример tolyanski более показательный, нежели пример из статьи, в статье всё таки EOF обрабатывается прямо в методе.
В Go действительно для таких случаев не помешал бы какой-нибудь оператор возврата ошибки по типу "?" из Rust
eee
Лукавите, в приведенном коде на go никакой обработки ошибок нет, а просто делается бабблинг ошибки наверх. В языке с исключениями не потребовалось делать ничего, т.к. исключения на то и исключения, что они автоматически бабблятся и тот кто захочет обработать, тот и обработает try-catch-ем.
kotlomoy
В данном случае никакой индивидуальной обработки нет, поэтому try...catch будет на вызывающей стороне.
Но я согласен, что исключения ещё хуже. Но что авторам языка мешало сделать синтаксический сахар, как в Rust, с неявным возвратом err в случае отсутствия обработки ошибки на месте? Тогда код был бы с первого примера, но под капотом бы работал код второго примера
tolyanski
Именно. Я не топлю за исключения как таковые, я топлю за читаемость кода
kotlomoy
Наша команда недавно начала писать на Го один из наших сервисов. Мы уже задались вопросом, почему в Го приняты сокращения в названиях переменных. Моя версия - чтобы компенсировать зубодробительные названия функций)
В Го мало того, что нет перегрузки функций, так ещё и нет file scope для названий функций. Поэтому даже если функция используется в пределах одного файла, ей нужно давать имя, неконфликтующее с другими функциями во всем пакете. Почему так? Чем авторам языка так насолил обычный static из C?
tolyanski
Выход один: больше пакетов хороших и разных)
Как и всегда: то что разработчикам языка реализовывать лень, они в итоге преподносят это всему миру как "осознанную фичу дизайна".