Всем привет! Сегодня я попробую вас убедить, что уникальным торговым предложением языка Go являются не горутины, скорость, минималистичность или современность, а "тоталитарность".
Что?
Преимуществами Go называют скорость, производительность, многопоточность, современность и удобность в микросервисах (чтобы это ни значило), и часто эти свойства выделяются как что-то уникальное.
Но разве в Java нет горутин? Разве Python какой-то старый? Разве Rust не быстрее? Мои коллеги не правы, потому что Go уникален не горутинами, а тоталитарностью.
В Go вы не будете писать элегантный код, не будете писать красивый код, не будете делать магию.
В Go нет наследования, поэтому мы будем использовать композицию.
# В питоне есть наследование class MapCache: def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ... class RedisCache(MapCache): def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ...
// А в Go - только композиция и интерфейсы type UserService struct{ cache Cache } type Cache struct{ v map[string]string } func (r *Cache) Get(k string) (string, error) {/* ... */} func (r *Cache) Set(k, v string) error {/* ... */}
В Go нет классов, перегрузок и конструкторов, поэтому мы пишем глупенькие структуры.
// Будь в Go конструкторы, выглядели бы они, наверно, так type User struct{ ID uuid.UUID Name string CreatedAt time.Time constructor(name string){ this.Name = name this.CreatedAt = time.Now() this.ID = uuid.New() } } // Но их нет, поэтому делаем так type User struct{ ID uuid.UUID Name string CreatedAt time.Time } func NewUser(name string) User{ return User{ Name: name, CreatedAt: time.Now(), ID: uuid.New(), } }
В Go нет макросов и метапрограммирования (почти), поэтому мы пишем if err != nil через каждую строчку

В Go вы будете писать одинаковый код, глупый код, скучный код, читаемый код.
И это будет прекрасно.
Горутины, производительность и минималистичность важны, но именно тоталитарность делает Go уникальным.
Под тоталитарностью в данной статье имеется в виду ограниченность Go, которая, например, запрещает разработчикам реализовать одну и ту же фичу 2 разными способами (ну, или наказывает их за это, но об этом позже). Автор, конечно, осуждает любой политический тоталитаризм и использует этот термин лишь для красного словца.
Давайте разберем каждый пункт поподробнее!
Горутины
Многопоточка Go прекрасна, она прибита гвоздями в язык, в стандартной библиотеке есть все что нужно для синхронизации потоков: мьютексы, атомики, каналы, вейтгруппы.
Но что такое горутина? По определению из go tour горутина это лайттред, который управляется рантаймом Go, в отличие от платформенных тредов, которые управляются операционкой (linux, mac и тд).

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

В Java 21 появились виртуальные треды, которые... определяются как лайттреды, которые управляются JVM.
Я не утверждаю, что горутины и виртуальные треды одинаковы, у них есть различия на уровне архитектуры, но для бекендера их использование одинаково - и там и там управление идет рантаймом, и то и то работает быстро, и там и там можно "наспавнить" тысячи лайттредов.
А в Python есть корутины, а в Rust есть async функции.
Корутины?
Очевидно, Python корутины - совсем не то же самое что и горутины. Архитектура и модель совсем другие, но решаемая задача та же: платформенные треды - медленно, поэтому нужно что-то быстрое и легковесное.
Да, горутины-корутины есть у всех — но в Go есть только горутины, и это часть тоталитарности.
Пояснение
Имеется в виду, что в Go не получится параллелить код, например, через обычные треды, корутины или что-то еще, если только не менять компилятор или делать что-то подобное. Да и зачем? Горутины прекрасно покрывают все нужды.
К чему это я? Любой современный язык давно понял, что на OS тредах далеко не уехать. Поэтому не убедим мы джавистов или растовщиков переходить на Go только из-за горутин.
Производительность
Я отлично понимаю, что могу развести святую войну этой темой, поэтому сразу предлагаю начать спор в комментариях о том, кто быстрее, но давайте по порядку.
Если очень грубо классифицировать языки по производительности, я задал бы 3 тира:

Тир 2 - Языки со сборщиком мусора, динамически типизируемые, интерпретируемые*, single-threaded/с GIL*.
В защиту Тира 2
* - не обязательное свойство
Да, у JS есть bun и JIT, у Python есть pypy, а PHP, оказывается, может достигать скоростей Java, но средний код на нативном интерпретаторе языка 2 тира почти всегда будет медленнее, чем код языка 1 тира.
Тир 1 - Языки со сборщиком мусора, но уже компилируемые, строготипизируемые, многопоточные.
Swift и GC?
Swift использует ARC, что не совсем классический сборщик мусора, но освобождает разработчика от ручной очистки хипа, из-за чего он ближе по произвоидельности и удобству к Go и Java, чем к языкам без GC.
Тир 0 - Языки без сборщика мусора.
А другие?
Важно отметить, что в данном тире разбираются только популярные языки в бекенд разработке общего назначения. Куда запихнуть SQL и 1C - понятия не имею :)
К чему это, снова, я?
Если мы перепишем средний бек с Python на Go, то, почти всегда, получим буст в ~3-5 раз по RAM, CPU, P50 и P99.
Например бенчмарк
Пример бенчмарка тута под спойлером "бенчмарки"
Если перепишем с Go на Rust - буст будет поменьше, P50 и P99, скорее всего, поменяются слабо, зато загрузки CPU и RAM упадут.
Но в Python же есть быстые библиотеки!
Вы можете мне сказать, что Python можно хорошо оптимизировать и если использовать быстрые библиотеки (написанные на Rust и C) типа pydantic, asyncpg и тд, то результат может быть сравним с Go. Но, конечно, на это можно ответить, что и в Go есть быстрые библиотеки (Fiber и Sonic - частично написан, кстати, на asm). Но даже без таких оптимизаций Go будет все равно быстрее - GIL и динамическая типизация накладывают слишком много оверхеда.
Иными словами переход из одного тира на другой дает прирост производительности.
А что внутри тиров? Джава же медленнее голэнга, потому что...
А почему? Реальность такова, что современная джава такая же, как и Go. И там и там есть GC, и там и там компиляция (только в Java она JIT, а в Go - сразу, то есть AOT). И там и там - строгая типизация, всякие оптимизации типа инлайнинга, и там и там лайттреды.
Хороших полноценных бенчмарков я не нашел, кроме одного - это видео от Антона Путры, классного блогера, что делает полноценные бенчмарки с кубером, промом и AWS.

По бенчмаркам Go и Rust видно, что Go быстрее умирает и в среднем тратит заметно больше CPU.

А вот между Java и Go разницы меньше, вроде P99 у Go меньше, но всего на 100 микро секунд, вроде CPU ниже, но всего на 1.5%.
Всего 400 RPS?
Важно отметить, что тут нагрузки детские, 400 РПС - далеко не предел для Go и Java. Поэтому если вы найдете хорошие бенчмарки - кидайте в комменты, буду очень благодарен!
Получается, что Java и Go одинаковы в производительности? Не совсем, как мне кажется, есть 3 важных различия:
История: Если взять средний Java бекенд и переписать его на средний Go бекенд, то вы получите огроменный буст, потому что средний Java бек будет сидеть на Java 8 (простите), которой уже больше 10 лет, а также использовать тяжелый Spring Boot. В то время как в Go приняты легковесные фреймворки типа Gin или Fiber, а также редко используются ORM. Конечно, со временем это меняется, но динамика, все-таки, осталась.
JIT vs AOT. Благодаря AOT (и не только) гошный сервис быстрее запуститься, меньше скушает ОЗУ на старте. Но джавовский JIT позволяет в рантайме оптимизировать узкие места и делает он это крайне успешно.
Идентичность с точностью до фреймворков. Разница между Go и Java будет крайне заметной, с точностью до фреймворков и библиотек. Используете не настроенный Java Spring Boot, против Go Fiber - Java проиграет.
ОЧЕНЬ грубо говоря, представим, что у вас есть эндпоинт, и гошный бек выдает на нем P99 - X ms. Тогда джавовый холодный такой же функционально бекенд будет выдавать 1.01*X ms, но при нагрузке задержка упадет до 0.99*X ms.
Скрытый текст
Опять же, если у вас есть бенчмарки и дополнения - скиньте! Обязательно добавлю в статью
Подводя итог - хороший быстрый бекенд можно написать как на Java, так и на Go, так что наших любимых джавистов мы не переубедим, что Go круто только из-за производительности
Почему я так хочу переубедить джавистов?
Потому что преимущества Go перед Python и JS очевидны - производительность и строгая типизация, что уменьшает потенциальные баги в продакшене. А вот сравениние с Java уже не такое очевидное :)
Микросервисы
Go прекрасно чувствует себя в куберах и докерах. Он компилируется в маленький standalone binary, не требует зависимостей (почти), запускается быстро, кушает мало ОЗУ.
Как-то я проводил анализ в своих проектах, изучал размеры докер образов одинаковых бекендов. Получилось, что Python, в среднем, кушает под 500 МБ, Java - 300, а Go - 100.
Пояснение
Это лишь мои наблюдения в разных командах и проектах. Хорошей статистики по этому вопросу я, к сожалению, не находил
Кстати, да, у Go можно срезать еще 70 МБ, если использовать образ distroless, у джавы под 100МБ, но динамика останется той же - Go занимает меньше всего места.
Получается очень удобно, но... Так ли это нужно? Нужно ли вообще сокращать расходы на container registry, где хранятся наши образы? Да в целом нет, редко найдется комманда в которой расходы на него будут превышать 1% от общих расходов на инфраструктуру. А если и найдется, то вы точно что-то делаете не так :)
Важно отметить, что Go сияет в разработке самой инфрастуктуры, Виктория Метрикс, Докер, Кубер, Бек графаны - все написано на Go. Малый бинарь, быстрый старт - идеально для таких задач.
Но если мы делаем обычный продуктовый бекенд, то так ли нам нужен Go?
Скажем, делаем мы 3 микросервиса на 3 подах, каждый кушает по 100МБ RAM и нагружен на 1-3 РПС. Java бы тут скушала намного больше, но... Но зачем вам тогда микросервисы? Не проще ли пихнуть все в монолит и получить еще меньший сумарный след?
Но тогда и Java будет выдавать тот же результат, потому что прогретая джава сравнима по RAM с Go. Пилить микросервисы на каждый чих - это скорее антипаттерн, лучше делать микросервисы побольше. Важно отметить, что Go, как мне кажется, самый удобный из всех популярных ЯП для бекенда, но это лишь вопрос удобства и процессов.
Иными словами не убедим мы джавистов, что надо переходить на Go :)
Но тоталитарность Go и тут прослеживается. Go компилируется только в standalone-binary, никакой интерпретируемости или JIT, никаких подключаемых в runtime зависимостей и тд.
А как же CGO и Plugin?
Ну, кроме как Сишных зависимостей. Гошные же библиотеки вкомпилируются в бинарь сразу, стабильного ABI или каких-либо еще систем подключения зависимостей после компиляции нет
Плагины? Чисто технически в Go есть режим компиляции в плагины (что-то вроде .so из мира C++), но они урезаны, не очень стабильны, не работают на Windows (и хорошо), а также могут вызывать краши, если вдруг плагин был собрал в одной версии Go, а код запускается через другую. Да и я ни разу не видел, чтобы их кто-то использовал :)
Современность
Go - супер современный язык, в нем встроенно все что душе угодно:
Package manager
Система тестов
Линтеры
Унифицированный стиль написания кода
Горутины
Встроенная библиотека большая, тут и все возможные сериализации, встроенный http сервер и клиент, работа с массивами и маппами и тд
В Go легко сделать бекенд только на STD либе.

Но это лишь необходимое условие для популярности языка. Мы, ленивые разработчики, не хотим писать CMakeList как на 1 курсе ВУЗа, мы хотим сразу все готовенькое и если завтра выйдет язык без менеджера пакетов, он будет пригоден только для сессий развлекательного программирования на ютубе, не для энтерпрайза.
При этом тоталитаризм и тут не потерялся. Единый package manager и система тестов уменьшают разнообразие подходов, зачем использовать кастомные тесты, если есть стабильные стандартные?
Аналогичная ситуация прослеживается со встроенной библиотекой, например, с net/http клиентом и сервером. Зачем искать third-party решения, если есть стандартные, которые и так все используют :)
А как же fasthttp?
Да, есть Fasthttp и Fiber, но они достаточно не популярны. Например, Traefik, Caddy и Victoria Metrics все используют net/http
Еще интересный факт - Fasthttp был создан создателем Victoria Metrics, но в ней он не используется, хотя казалось бы :)
Минимализм
Go минималистичен, Go строг, Go однообразен, в Go всего 25 ключевых слов (против 35 в Python, 50 в Java, 90 в C++)

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

В Go не обязательно все оборачивать в static public final class
В Go не пописать красивых макросов
В Go не обернуть обьект в Box<Cow<Arc<RefCell<'static str>>>>
Вы скажете "Но это же минус!"
А давайте разберем конкретный пример
Полиморфизм
Скажем, есть у нас класс UserService, которому нужен кеш. Вначале сделаем кеш простой, на одной мапе без мьютексов.
type UserService struct{ cache Cache } type Cache struct{ v map[string]string } func (r *Cache) Get(k string) (string, error) {/* ... */} func (r *Cache) Set(k, v string) error {/* ... */}
Проходит время, мы понимаем, что разным классам нужен разный кеш, где-то тредобезопасный, где-то на редисе. В Go мы можем решить нашу задачу только 1 путем - интерфейсы и композиция: создаем Cache interface с методами Get и Set и реализуем наши кеши.
type Cache interface{ Get(k string) (string, error) Set(k, v string) error } type MapCache struct {/* ... */} type MapCacheThreadSafe struct {/* ... */} type RedisCache struct {/* ... */}
Скрытый текст
В реальном кеше, конечно, стоит добавить context.Context, TTL, инициализацию и закрытие
Как же это можно в Python?
Можно, например, сделать на протоколах, по сути, тех же неявных интерфейсах, что и в Go.
@dataclass class UserService: cache: Cache class Cache(Protocol): def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ... class MapCache: def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ... class RedisCache: def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ...
Можно на абстрактных классах, говоря на гоферском - явных интерфейсах.
class Cache(abc.ABC): @abc.abstractmethod def get(self, k: str) -> str: ... @abc.abstractmethod def set(self, k: str, v: str) -> None: ... class MapCache(Cache): def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ... class RedisCache(Cache): def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ...
Можно на наследовании 2 способами: создать BaseCache (де-факто абстрактный класс), либо от MapCache отнаследовать RedisCache
class MapCache: def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ... class RedisCache(MapCache): def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ...
Либо мы можем просто проигнорировать типизацию, это же Python :)
Так чем же Go лучше?
Он ограничивает. Мы, гоферы, на код ревью будем спорить про названия переменных, расположение модулей, инвалидацию кеша, но не про наследование против композиции. Golang уменьшает количество конфликтов между разными разработчиками. В свою очередь это позволяет проще перекатиться из одного проекта в другой, проще поконтрибьютить в опенсорс, посаппортить чужой старый код.
Go однообразный, одинаковый, простой, тоталитарный.
А как же embedding?
Тут вы можете сказать, что в Go есть embedding и данную задачу можно решить через него. Но... Нельзя. Embedding не позволит нам переопределить методы уже существующего класса, только зашедуить их. Поэтому нам в любом случае придется делать интерфейс, реализовывать его и использовать в UserService.
Тоталитарность
Тот же Python тоже минималистичен, но Go идет еще дальше - он ограничивает.
В Python есть прекрасный дзен с 2 хорошими правилами.
$ python3.14 -c "import this" | head -n 5 | tail -n 2 Explicit is better than implicit. # Явное лучше неявного Simple is better than complex. # Простое лучше сложного
Go возводит их в абсолют.
Хотите распаралелить код? Только горутины. Вам не нужно проверять, что нужный вам коннектор к новой базе данных работает на горутинах, потому что в Go есть только горутины.
Небольшое отступление
Интересное следование. Не будь в Go сразу горутин, а только треды (как в Python и Java изначально), со временем пришлось бы их (или что-то аналогичное) добавить, что привело бы к зоопарку решений, прям как в Java и Python. А это в свою очередь нарушило бы тоталитарность и единство.
Хотите заимпортить пакет? Значит указываете весь путь до репозитория и импортите сам модуль. Вы не можете заимпортить только конкретную функцию или обьект.
# Python from pydantic import BaseModel from pydantic import * # Можно заимпортить все из модуля, только модуль, что хотите!
// Go import "github.com/pkg/errors" // Нельзя импортунуть только метод "New" errors.New("new error!")
Опять же, меньше споров на тему стиля!
Пояснение
Имеется в виду, что часть питонистов выступают против import *, а Go решает эту проблему - просто нельзя заимпортить через звездочку!
Все языки умеют говорить на C и Go тут не исключение, через CGO можно запускать сишный код, но сам CGO имеет оверхед, поэтому в узких местах, возможно, сишный код будет лучше даже переписать на Go
Например
CGO не стоит использовать, например, если вы очень часто вызываете очень маленькую функцию. CGO лучше подойдет, скажем, с батчами, когда делается сразу много операций в пространстве C
А если неосторожно влезть в подкапотную Golang, то можете оказаться на публичной доске дураков (в оригинале "hall of shame")! В стандартной библиотеке есть такая функция - runtime.throw, она доступна только из пакета runtime. Сама функция является чем-то вроде суперпаники, останавливает процесс, пишет ошибку, отловить нельзя. Но чисто технически с ней можно слинковаться и использовать в сторонних библиотеках.

И это плохо! Мейнтейнерам языка Golang это не нравится, поэтому в самих исходниках Go есть список библиотек, которые находятся в официальном зале позора из-за использования указанной функции.
Например, туда попали:
xray-core - VPN протокол
simdjson-go - minio JSON парсер
pebble - хранилище, используемое в CockroachDB
Как мы видим, Golang явно дестимулирует пользователей лезть во что-то сложное и внутреннее
А почему runtime.throw-то нельзя использовать?
Как я понял, проблема заключается в том, что разработчики Go могут хотеть поменять сигнатуру или логику работы throw, но так как на нее уже кто-то завязался - изменение сломает их проекты. И хоть это не будет нарушением обратной совместимости, потому что это не публичная функция, все равно не хочется так делать :)
Хотите перегрузок операторов? А их нет... Вообще ни перегрузок, ни перегрузок операторов, ни оверрайдов нет.
package main import "fmt" // Пример класса `вектор`, // в котором можно было перегрузить оператор +, будь мы в C++. // Но в Go это сделать невозможно. type Vec3[T int64 | float64] [3]T func (l Vec3[T]) Add(r Vec3[T]) Vec3[T] { return Vec3[T]{l[0] + r[0], l[1] + r[1], l[2] + r[2]} } func main() { vec1 := Vec3[int64]{1, 2, 3} vec2 := Vec3[int64]{4, 5, 6} fmt.Printf("%+v\n", vec1.Add(vec2)) // not vec1 + vec2 }
Хотите выполнить какой-то код на compile time? Неа, не получится, максимум что есть - кодогенерация.
Хотите перечисления (enum)? Нет не хотите. В го нет ни только алгебраических енамов, в го нет даже обычных енамов. Идеоматичный (то есть верный) способ - создать свой alias-тип и константами обьявить перечисления, но такой подход не может дать гарантию, что кто-то случайно не положен что-то не то в ваш enum.
Скрытый текст
Можно, конечно, использовать кодогенерацию для автоматического создания сериализации/десериализации, но, опять же, гарантии валидности енама достичь невозможно. Максимум - частично закрыть через линтеры.
Управление потоком в го происходит максимально явно. Исключений нет, только явные ошибки, поэтому всегда видно когда функция может преждевременно завершиться. В го нет аннотаций, макросов или декораторов, которые меняют поведение функции, поэтому если вы видете тело функции, то всегда можете быть убеждены, что именно оно будет выполняться.
import ( "github.com/gofrs/uuid" "github.com/pkg/errors" ) func (r *Service) getUser(id uuid.UUID) (User, error) { userDB, err := r.userRepo.getUser(id) if err != nil { return User{}, errors.Wrap(err, "get user") // Ошибка может быть тут } user := userDB.toDTO() if len(userDB.subscriptions) != 0 { subscriptions, err := r.subscriptionRepo.getSubscription(userDB.subscriptions) if err != nil { // Ошибка может быть и тут return User{}, errors.Wrap(err, "get subscriptions") } user.subscriptions = subscriptions if user.balance < 0 { // А тут может быть паника panic("user invalid state") } } return user, nil // А тут все ок! }
Про panic
Да, в Go есть panic, который работает похоже на исключения и чисто технически можно его так и использовать. Я даже находил библиотеки, что panic так и используют.
Но есть важный ньюанс - в самом комьюнити Go не принято использовать панику, даже если очевидно, что какая-то ошибка невозможна. Например, зачастую можно гарантировать, что сериализация JSON будет всегда успешной, но ошибка из json.Marshal разработчиками, в основном, все равно обрабатывается, а не паникуется.
Гоферы очень любят слово "идиоматично". Что же это значит? Это правильно. Не быстро, эффективно, адекватно, просто правильно. Почему правильно? Потому что так принято.
Зачем?
Что же дает тоталитарность гоферам?
Легкость перекатываний. Вас заставили поддерживать чужой старый монолит? Хотите исправить в баг в опенсорс либе? Нужно заонбордить джуна? С Go это проще, он тоталитарный.
Минимизация конфликтов. Меньше споров на ревью про паттерны! Теперь спорим только про переменные и инвалидацию кеша.
Производительность. Как это странно бы ни звучало, но из-за соблюдения обратной совместимости, обновить версию вашего гошного проекта с 1.18 до 1.26 - крайне просто. А новые версии часто ведут за собой оптимизации, например, новый GC. Также еще одно странное явление гоферов - крайняя нелюбовь к ORM, из-за чего разработчики частенько самостоятельно пишут SQL запросы, что тоже, скорее, позитивно сказывается на производительности.
Вайбкодинг
Кстати. Вайбкодинг с Golang, наверно, самый простой из всех существующих языков. Это связанно с той же тоталитарностью, а также с крайне высокой стабильностью языка, обратная совместимость в Go религиозно защищается, из-за чего даже если ЛЛМ обучалась на старенькой 1.18 версии Go, она и в 1.26 версии будет писать корректный код
Из-за чего?
Тоталитарность Go не случайна. Go, де-факто, создавался Гуглом для решения проблем Гугла - тысячи разработчиков, проекты постоянно закрываются и открываются, нужен язык, который будет быстрым, простым и разрабов будет легко перекидывать между коммандами.
Собственно так и получилось :)
Минусы?
Очевидно, такая тоталитарность создает массу проблем, бесконечные if err != nil, могут легко захламить код и замазать глаз, отсутствие дженериков до 1.18 версии (я даже не буду объяснять в чем тут проблема) и другое. Отсутствие enum иногда тоже выстреливает неожиданными багами, да и многопоточка могла бы быть побезопаснее
Пример
Например, как в Rust, где мьютексы закрывают доступ к конкретному объекту и не позволяют к нему обратиться без Lock, а не просто существуют в вакууме как в Go.
Да и отстувие stack trace в стандартных ошибках - крайне странное решение, которое приводит к third-party библиотекам типа pkg/errors или cockroachdb/errors и небольшому, но все же зоопарку обработки ошибок.
Написанием этой статьи я не хотел вам "продать" Go, я лишь хотел обратить ваше внимание на особенность Go, которую я считаю самой важной.
Спасибо за внимание
Обязательно оставляйте комментарии, буду рад с вами поспорить!
Комментарии (50)

santanico
23.03.2026 10:55Кажется вы не до конца понимаете значение слова тоталитарный) Go не заставляет и не запрещает, он просто дает набор возможностей. Возможно набор небогатый, но достаточный)
Более того, насколько я понимаю, он особо не ограничивает фантазию. Кто-то может работать с ошибками через panic() или глобальную last_error, синхронизировать goroutines через классические мьютексы и эмулировать наследование используя generics, интерфейсы и embedded structs (хотя учитывая то как хейтят сейчас ООП, думаю таких найдется немного)

TeaDove Автор
23.03.2026 10:55Go как раз заставляет, запрещает и дистимулирует
Можно ли работать с ошибками через panic? Можно, но это очень не удобно, потому что в std и сторонних библиотеках принято использовать error, получится смешение подходов, которые еще нужно как-то соединять
Можно ли сихронизировать горутины через классические мьютексы? Да, но зачем, если есть sync.Mutex?Можно ли эмулировать наследование через embedding? И да и нет, полноценного наследования все-равно не получится. Методы и поля через embedding невозможно переопределить так
Возможно набор небогатый, но достаточный)
Согласен, в этом и суть! Есть все, что нужно для бекенд разработки, ни больше ни меньше. В этом и суть "тоталитарности", дать разрабу достаточно функциональности, чтобы он мог эффективно писать код, а также чтобы он не изобретал велосипед, при этом не слишком много фичей, чтобы он не было зоопарка реализаций

santanico
23.03.2026 10:55Я понимаю вашу точку зрения и сам лично приветствую философию Go. Но вот только «тоталитарный» это совсем другое.
Это не когда, «неудобно», «некрасиво» или «не нужно», а когда запрещено и жестко наказуемо. И не собственным чувством прекрасного, а государством или близким к нему по степени власти над вами органом.
Можно было бы привести примеры, но не хочется тут уходить в политику, каждый может сам найти при желании.

Dhwtj
23.03.2026 10:55В богатой доменной модели Go плетётся в хвосте.
Rust > C#>Java>PHP>Go
Rust - sum types (enum), pattern matching, трейты, ownership - отлично ложится на domain modeling в духе Влашина ("Making Illegal States Unrepresentable").
C# подтянулся: records, discriminated unions пока нет нативно. Java - sealed classes, но многословно. PHP - слабая типизация мешает.
Go в хвосте: нет enum с данными, нет дженериков до недавнего (и они слабые), error handling через if err != nil - домен тонет в бойлерплейте. Structs + interfaces - плоская модель, сложные инварианты выражать тяжело.
Но Go и не для этого. Его ниша (ой, тут утверждали про тотальность/универсальность?) - сетевые сервисы с простой доменной логикой, где anemic model + процедурный стиль нормально работает. Проблема когда на Go пытаются писать сложный домен - получается месиво.
Второй большой недостаток - го не может вызвать или быть вызванным из других программ, только по сети.
Нет иммутабельности значит нет функционалльного программирования нормального, но ООП тоже нет. Остаётся процедурный стиль со структурами и интерфейсами. По сути это улучшенный C из 70х с GC и горутинамиЭто с чем столкнулся
Ну и ещё десяток недостатков...
TeaDove Автор
23.03.2026 10:55Согласен, собственно поэтому Go и используют только в бекенде и инфраструктуре бекенда, а в геймдеве, фронте, мобилке, системной разработке и тд его почти никогда не используют

Dhwtj
23.03.2026 10:55Тогда непонятно о чем статья
Про философию Go "one way to do it"?
TeaDove Автор
23.03.2026 10:55О основной фишки голэнга - тоталитарности, что ограничивает возможности разработчика
Я не писал о том, что Го надо использовать везде и ко всему или что его дизайн самый лучший, я писал о том, что сам Го уникален не горутинами, а ограничениями
И это не плюс или минус, это как функциональшина хаскеля или боровчекер в расте - фишка языка, которая определяет его и его использование

Siemargl
23.03.2026 10:55Го создан не для разработчика, а для себя, любимой корпорации:
Функциональность только для нужной себе области применения
Замена Явы, чтобы не зависеть от кокурента
Лёгкая замена дешёвых разработчиков на галерах

Tuxman
23.03.2026 10:55Почему в Go не как не завезут оператор '?' как в Rust? Не было бы такого бойлерплейта
f, err := os.Create(filename) if err != nil { return err }было бы
f := os.Create(filename)?
Dhwtj
23.03.2026 10:55Много чего нет. Вот более полный паттерн, мой любимый
Обработка ошибок не загрязняет бизнес логику. Функция read_port - две строки чистой логики, оба ? невидимо делают конвертацию через From. Вся механика ошибок вынесена в определения типов, которые живут отдельно.
В Go та же функция - половина тела это if err != nil { return ... }. Обработка ошибок перемешана с логикой 1:1
Это то что Скотт Влашин называет "railway oriented programming" - happy path читается линейно, а ошибочный путь идет параллельно и не мешает
Аналог в веб: main() - контроллер, read_port - сервис
use std::fs; use std::num::ParseIntError; use thiserror::Error; #[derive(Debug, Error)] enum ConfigError { #[error("failed to read config")] Io(#[from] std::io::Error), #[error("failed to parse port")] Parse(#[from] ParseIntError), } fn read_port(path: &str) -> Result<u16, ConfigError> { let content = fs::read_to_string(path)?; // IoError -> ConfigError::Io автоматически let port = content.trim().parse::<u16>()?; // ParseIntError -> ConfigError::Parse автоматически Ok(port) } fn main() { match read_port("/etc/app.port") { Ok(port) => println!("port: {port}"), Err(e) => eprintln!("Error: {e}"), } }И аналог
package main import ( "errors" "fmt" "os" "strconv" ) type ConfigError struct { Op string Err error } func (e *ConfigError) Error() string { return fmt.Sprintf("%s: %v", e.Op, e.Err) } func (e *ConfigError) Unwrap() error { return e.Err } func readPort(path string) (uint16, error) { content, err := os.ReadFile(path) if err != nil { return 0, &ConfigError{Op: "failed to read config", Err: err} } port, err := strconv.ParseUint(string(content), 10, 16) if err != nil { return 0, &ConfigError{Op: "failed to parse port", Err: err} } return uint16(port), nil } func main() { port, err := readPort("/etc/app.port") if err != nil { var ce *ConfigError if errors.As(err, &ce) { fmt.Fprintf(os.Stderr, "Config error: %v\n", ce) } return } fmt.Printf("port: %d\n", port) }Эргономика - главное отличие. В Rust компилятор заставляет обработать каждый вариант ошибки (match должен быть исчерпывающим). Добавил вариант в enum - компилятор покажет все места где не обработал. В Go ошибка это error интерфейс, забыл проверить конкретный тип - узнаешь в проде
Но в бедной бизнес логике - в мире го - это не существенно
Если сервис - CRUD с минимальной логикой, типичный микросервис на 5 эндпоинтов, то Go-шный бойлерплейт терпим, а Rust-овые типы избыточны. Проблемы начнутся при росте сложности (рост числа вариантов ошибок, числа слоёв)

Dhwtj
23.03.2026 10:55И мусор в логах го. То есть искать не по типам ошибок (Rust, C#это элементарно), а по конкретным сообщениями. Как там ELF настраивать в таких условиях даже не знаю.
Мрак
Но никто так же делает. Просто сервисы долго не поддерживаются, переписывают полностью вместе с ELF настройками.
Свистоперделки пожалуйста, только не пишите на го долгоживущий код.
Вы спросите, как же пишут долгоживущие проекты вроде кубера? Всё плохо: там полно кастомных слоёв-костылей поверх языка.
В частности костыль для обработки ошибок, в кубере свои типизированные ошибки через
k8s.io/apimachinery/pkg/api/errorstype StatusError struct { ErrStatus metav1.Status } // metav1.Status содержит Reason, Code, MessageТак что из-за таких сложностей написание кубер на Go сложнее чем если бы он был на Rust. С использованием LLM, конечно) это сильно снижает порог входа в Rust

Siemargl
23.03.2026 10:55В обработке ошибок паритет - глаз привычно пролистывает простыни if err в Го, а в расте меньше строк но надо приглядываться к знаку вопроса в конце
Так что из-за таких сложностей написание кубер на Go сложнее чем если бы он был на Rust.
А это уже смена темы и передергивание. От того что у Го не самая удачная обработка ошибок, недостатков раста это не перекрывает.
"если бы" - ну так напишите.

vcKomm
23.03.2026 10:55TL;DR: даже в Гугле не смогли придти к единому решению, как лучше обрабатывать ошибки; языку уже 15 лет, уже поздно такое делать; так будет два способа делать одно и то же; неудобно дебажить (нельзя воткнуть принт в `if err != nil`)

ddv2005
23.03.2026 10:55Как можно сравнивать Go и Java ? Java это тормозное корпоративное болото в котором никто не считает никакие ресурсы...ни машинные, ни человеческие. Go программа будет работать на 90% target машин сразу,а Java вы сначала замучаетесь искать нужную версию,а потом все бросите и пойдете переустанавливать новую систему потому что нужной версии java нет даже для не старой версии системы. Вот как раз недавно имел занимательное развлечение с UniFi network server который вдруг решили обновить Java 17 на Java 25, только эти "эффективные" программисты забыли что рекомендуемая ими Debian 12 Bookworm (вышедшая всего 2 года назад) НЕ ИМЕЕТ Java25 headless. Т.е. теперь все клиенты должны или обновить систему до Debian 13 или поставить гигабайты X11+ вместе с полной версией Java25 , чтобы просто работал простенький web server. Думаю что если бы они писали на Go, то все уместилось бы в десяток мегабайт и работало бы на системах 20 летней давности. Вот поэтому люди и выбирают Go, а не потому что в Java тоже НЕТ горутин.

freemorger
23.03.2026 10:55Щас не понял, композиция это реально минус для кого то? Надо быть тяжело промытым в мозгах, чтоб любить наследование

ayevdoshenko
23.03.2026 10:55Любить наследование необязательно, а вот уметь использовать, где оно есть - надо : )

ddv2005
23.03.2026 10:55А может быть это с вами что то не так раз вам проще 100 раз сделать Copy-Paste вместо наследования? Я люблю наследование, более того...бывает что в языках, отличных от С++ мне не хватает множественно наследования. И у меня никогда небыло проблем с поддержкой проектов с множественным наследованием. Если же язык не поддерживает классы и наследования, то все равно их концепция может быть реализована, просто будет намного более не красиво и сложнее в поддержке, но вы все равно тем или иным способом будете это использовать потому что альтернатива это copy-paste

TeaDove Автор
23.03.2026 10:55У наследования есть прекрасная не copy-paste альтернатива - композиция :)

ddv2005
23.03.2026 10:55Вы наверное не понимаете что такое композиция и чем она отличается от наследования.Композиция работает когда вы хотите использовать готовый функционал готового класса и ему не нужен доступ к вашему классу потому что он уже полностью готов и самодостаточен. Вы не можете переопределить ничего при композиции не меняя интерфейсам взаимодействия с классом, который используете при композиции. А наследование подразумевает что родительский класс имеет полный доступ к наследнику и вы можете менять логику родительского класса БЕЗ изменения интерфейса взаимодействия. Возмем простой пример. У вас есть класс который реализует список (list) чисел вы ходите сделать класс который его расширяет и делает его упорядоченным списком. При наследовании я просто наследуюсь от него и переопредяляю метод добавления чтобы он вставлял не в конец, а в нужное место согласно нужному порядку....все, переопределил 1 метод и готово. А теперь сделайте тоже самое композицией чтобы я мог передать любой из этих классов одному методу. В лучшем случае для этого надо будет определить интерфейс, потом сделать класс заготовки списка с доступом ко всем его элементам, и только после этого вы сможет включить этот класс в композицию финальных классов где уже реализуете интерфейс используя copy-paste для обоих классов. И при каждом изменении интерфейса вы должны менять все где он упоминается и реализован. И что будет проще поддерживать и использовать?

Siemargl
23.03.2026 10:55Много букв, мало знаний.
Есть яп, для которых композиция неявно переписывается как вызов метода и наоборот.
Включая даже мультиметоды. Там же список яп

ddv2005
23.03.2026 10:55Мало букв, ноль знаний. Сразу видно "господина" теоретика, который не написал ни одной реальной строчки кода в своей жизни. В мире есть много чего (кроме конечно ваших знаний, вот чего нет так уж нет...даже больше нескольких предложений воды не осилили). Вы можете извращатся как хотите, это ваше дело как эмулировать наследование, но проще и лучше от этого не становится ни код ни его поддержка. Для начала автор хотябы должен разъяснить чем по его мнению композиция лучше наследования и как он использует эту "альтернативу" в конкретном примере. Ну от ответить лучше с аргументами, а не ссылками на wiki про мультиметоды пытаясь сойти за умного. Ум проявляется в делах, а не ссылках на чужие слова. Давайте пример как вы обходите наследование композицией с переписыванимем методов в месте со статьей как вы потом будете это поддерживать, если конечно вас раньше не сожгут на костре за такое издевательство. А потом и сравним что проще и кому промыли что то. Только делайте это на языке который поддерживает и наследование и композицию, а дальше хоть мультиметодами, хоть корягами, главное чтобы проще, нагляднее и понятнее чем наследование. Вот мы тут посмеемся.

Siemargl
23.03.2026 10:55Учебник по расту или ним вам в руки.
Я бы в плане удобств скорее придрался к возможностям модулей.

ddv2005
23.03.2026 10:55Мда, конкретика и аргументы достойны несостоявшегося программиста. Неужели все так плохо и такой теоретик не может даже аргумент сформулировать, раз уж код не получается. Вот вам ссылка что такое аргумент https://ru.wikipedia.org/wiki/Аргумент_(логика)

Siemargl
23.03.2026 10:55Я привёл конкретные языки, в которых есть расширенная диспетчеризация вызовов без классического ООП.
Куда уж конкретнее.
Удобно это или нет это уже вкусовщина.
А кругозор стоит расширять. А не утверждать "А у меня в
БейсикеПитоне сделано так, потому это аксиома"
ddv2005
23.03.2026 10:55Так вы еще и читать не умеете? Я вас разве спрашивал о списке языков в которых есть "расширенная диспетчеризация вызовов без классического ООП"? Нет. Я утверждаю что композиция это НЕ альтернатива наследованию если язык поддерживает наследование. Использование композиции в качестве альтернативы наследованию это костыли которые приводят к меннее наглядному, менее качественному и создают сложности с поддержкой кода. Если вы не согласны с моим утверждением, то жду ваших АРГУМЕНТОВ, а не списка языков и учебников.

Siemargl
23.03.2026 10:55Не согласен.
Как, очевидно, и разработчики вышеупонянутых языков.
Аргумент - они рекомендуют использовать композицию или же вообще не стали реализовывать наследование.
Я не так велик, чтобы выдавать свое мнение как единственно правильное =)

ddv2005
23.03.2026 10:55Вот тут вы полностью правы, вы НЕ так велики чтобы выдавать свое мнение за мнение разработчиков вышеупомянутых языков и не вам решать почему они приняли те или иные решения.

TeaDove Автор
23.03.2026 10:55Есть классный контраргумент против наследования
А зачем вам вообще может понадобиться сразу отсортированный список везде? Что это за реальная задача, где это нужно?Почему бы ни создать кастомный класс SortedList c методами Get(i int) T, Append(v T) и тд и использовать его?
А в случае (практически невозможном в реальности), когда вам вдруг понадобится в одном проекте в одном наборе функций использовать и SortedList и обычный List - можно использовать те же интерфейсы.И при каждом изменении интерфейса вы должны менять все где он упоминается и реализован.
Так это всегда нужно делать, если вы меняете интерфейс, то все функции, что используют этот интерфейс должны быть изменены. Но с наследованием же такая же история, если вы в List переименуете метод Get на GetValue, то вам везде придется поменять названия
Прелесть композиции и запрета наследования в Go в том, что Go гарантирует, что если у вашей функции есть 3 аргумента - структура, int и массив интов, то туда можно передать только структуру, int и массив интов, никаких дочерей, пасынков, родственников и друзей, только эти типы данных
ddv2005
23.03.2026 10:55Есть классный контраргумент против наследования
А зачем вам вообще может понадобиться сразу отсортированный список везде? Что это за реальная задача, где это нужно?
Это не контр аргумент, а демогогия. И почему везде? То то и оно что где то нужен обычный список в котором не надо тратить время на сортировку, а где то нужен отсортированный. Задача хоть и упрощенная но вполне проецируется на реальные задачами. Суть это примера не в конкретной задаче, а в необходимости переопределить небольшое количество методов которым требуется доступ к внутренним данным класса.
можно использовать те же интерфейсы.
Да можно, но наследование изящние (если оно есть в языке) и почему это плохо,а композиция хорошо мне так никто и не сказал.
Так это всегда нужно делать, если вы меняете интерфейс, то все функции, что используют этот интерфейс должны быть изменены.
Да неужели? Если я добавляю 25 новых функций в базовый класс то я могу вообще не менять ни одного наследника и все будет рабоать. А в случае с интерфейсом я ОБЯЗАН менять ВСЕХ наследников причем в большинстве случаев с композицией это будет copy-paste.
Прелесть композиции и запрета наследования в Go в том, что Go гарантирует, что если у вашей функции есть 3 аргумента - структура, int и массив интов, то туда можно передать только структуру, int и массив интов, никаких дочерей, пасынков, родственников и друзей, только эти типы данных
А в чем прелесть то, со стороны разрабочика на Go (если вы конечно не мазахист и каждое ограничение вызывает у вас восторг)? То что это проще для разработчиков языка, да, но для разрабочика на Go это ограничение. В таком случае вам понравится писать на чистом ассемблере...абсолютная прелесть. Да и для справки, в Go вооще нет классических классов, так что о каком наследовании классов вообще идет речь это загадка.Там есть структуры в которых нет методов и для структур композиция это и есть частный случай наследования.

TeaDove Автор
23.03.2026 10:55Композиция это не частный случай наследования, это совсем другое
Под классами я имел ввиду в Го, конечно, структуры, их тут часто называют классами, потому что и так понятно о чем речь (и методы, кстати, у структур есть)
Приведите, пожалуйста, реальную ситуацию, не изолированный воображаемый пример, когда наследование однозначно выгоднее
Прелесть Го в том, что никто не передаст в функцию непойми что, что не нужно разбираться какая функция в реальности вызывается у класса и что кто-то в дочернем классе переопределил её на бог знает что

ddv2005
23.03.2026 10:55Композиция это не частный случай наследования, это совсем другое
Не надо вытаскивать слова из контекста, я писал " есть структуры в которых нет методов и для структур композиция это и есть частный случай наследования". Если не верите, то возмите на с++ и напишите наследование и композицию для структур без методов и вы увидите что результат будет одинаковый. Я это лично проверял как разные компиляторы c++ располагают классы, структуры и таблицы виртуальных методов в памяти еще 30 лет назад.
Приведите, пожалуйста, реальную ситуацию, не изолированный воображаемый пример, когда наследование однозначно выгоднее
Выгоднее чего? Copy-paste реализации через интерфейсы. Так это аксиома что Copy-paste это плохо, а по другому там никак. Если вам нужны примеры, так откройте любую библиотеку на с++ где есть наследование и попробуйте подумать как это реализовать без наследования, а только композицией..да это будет возможно написать, но код будет намного сложнее чем через наследование.
Прелесть Го в том, что никто не передаст в функцию непойми что, что не нужно разбираться какая функция в реальности вызывается у класса и что кто-то в дочернем классе переопределил её на бог знает что
В каком смысле "не нужно разбираться какая функция в реальности вызывается у класса"??? Если функция в Go принимает ссылку на интерфейс то как раз нужно разбиратся что за немойми что это за экземпляр и кто реализует этот интерфейс и какая в реальности функция вызывается. и вполне возможно что что кто-то в дочернем классе реализовал вместо интерфейся бог знает что

TeaDove Автор
23.03.2026 10:55Да, в случае интерфейсов такое будет
А в случае НЕ интерфейсов в Го гарантировано передастсо именно то, что принимается, а не дочка

ddv2005
23.03.2026 10:55Ну а у в случае если вообще ничего не писать, так и вообще поддерживать легко...нет кода - нет проблем. Но мы же все таки пишем код и используем интерфейсы. Хотите реальный пример - есть у меня небольшой проект на Go - стриминг сервер, который раздает MPEGTS/HLS/SRT и т.п. Если бы я его писал на C++ то я бы сделал базовый класс входного потока и потом бы уже наследовался от него и реализовывал на его основе отдельно MPEGTS/HLS/SRT и т.п. Но в Go нет наследования и поэтому чтобы не плодить copy-paste реализуя все через интерфейс я переворачиваю эту модель задом наперед и делаю базовый класс входного потока финальным, а реализации MPEGTS/HLS/SRT через композицию в этот класс указателя на интерфейс имплементации конкретных протоколов,с неизбежными кросслинками между имплементацией и базовым классом. Вы думаете это упростило мне жизнь? да нихрена. Да все работает, нет copy-paste, но реализация в прямом смысле через зад - задом на перед. Это необходимые костыли, которые усложнили код и его поддержку.

ayevdoshenko
23.03.2026 10:55А в каком месте вы привели пример Композиции на Go?
Если вы про этот код:type UserService struct{ cache Cache } type Cache interface{ Get(k string) (string, error) Set(k, v string) error } type MapCache struct {/* ... */} type MapCacheThreadSafe struct {/* ... */} type RedisCache struct {/* ... */}Так тут, пардон, — её нет, это Агрегация.
Но главный вопрос к "тоталитарности" интерфейсов в Go:
В чем преимущества "гадания" в Go — подходит ли структура под интерфейс, который нигде не задается явно при реализации, — перед явным объявлением в Python?
class Cache(abc.ABC): @abc.abstractmethod def get(self, k: str) -> str: ... @abc.abstractmethod def set(self, k: str, v: str) -> None: ... class MapCache(Cache): def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ... class RedisCache(Cache): def get(self, k: str) -> str: ... def set(self, k: str, v: str) -> None: ...В Python я вижу, что класс наследуется от
Cacheи согласно LSP обязан следовать контракту родителя.В Go же, чтобы понять, что, например, структура
MapCacheвообще имеет отношение к интерфейсуCache, мне нужно откуда-то догадаться, что где-то объявлен такой интерфейс.Где здесь та самая "тоталитарность", которая якобы упрощает жизнь и снимает вопросы? И как насчет последовательности дизайна языка? Куда вдруг пропала явная типизация - и всплыла "уточка типизации"? : )

TeaDove Автор
23.03.2026 10:55Встраивание Кеша в UserService как раз пример композиции
Тоталитарность Го тут проявляется в том, что в питоне есть 5 способов, как можно сделать одну задачу. В данном примере можно протоколы использовать, а можно асбтрактные классы. Один разраб одно будет использовать, другой другое. А в Го - только интерфейсы, даже если интерфейсы тут хуже, чем абстрактные классы

ayevdoshenko
23.03.2026 10:55Go — замечательный язык для своей ниши: инфраструктурные сервисы, CLI-утилиты, всё, что должно компилироваться в один бинарник и предсказуемо работать. Его ограниченность — это плата за попадание в эту нишу, а не философский идеал. Назвать это "тоталитарностью" и представить как главное преимущество — красиво, но по сути это эвфемизм для "узкой специализации". Как только язык попытается выйти за пределы этой ниши, его "тоталитарность" начнет трещать по швам, и он неизбежно начнет обрастать теми самыми сложными конструкциями, до которых у фанатов языка и дело ввиду их сферы разработки редко доходит.

Siemargl
23.03.2026 10:55В Го ничего менять не будут.
Хотите изменений при гошном синтаксисе - см vlang

ayevdoshenko
23.03.2026 10:55Так и речь не о том, чтобы делать из Go язык на все случаи жизни, а о том, что автор сравнивает и "удивляется": и зачем в других языках все эти сложности, которых нет в Go? : ) Так это не потому, что Go нашёл программистский универсальный Грааль, а потому что это хороший инструмент для конкретной сферы. У других языков — другие сферы, отсюда и другой инструментарий.

TeaDove Автор
23.03.2026 10:55Так я, автор, и не говорю, что Go это Грааль или идеал, а просто хороший инструмент под конкретную задачу
(надо было секцию "Минусы" побольше описать...)
ayevdoshenko
23.03.2026 10:55А зачем тогда сравнивать его с языками, явно другого поля применения, такими как - Java, Python, да и Rust?
Вы берете, условно говоря, трековую машину - с максимально обтекаемым силуэтом и прижатую к земле, у которой из салона выкинуто всё "лишнее" а вместо этого "лишнего" - установлен каркас безопасности... и сравниваете её с седаном, на котором ездят на работу каждый день, или с пикапом, в котором грузы возят - и говорите: во, зачем им этот салон, где усилители, почему такие высокие - их же раскачивать будет в поворотах!... : )))
И опять - речь не про то, что Go, условно - плох на "бездорожье" : ) ему туда по-сути и не надо, а о том, что если уж и имело смысл сравнивать - так это "машины" одного назначения.
А если делать "многоцелевую" машину - то вот тут и полезут компромиссы : )
TeaDove Автор
23.03.2026 10:55Так Rust, Python и Java используют часто в тех же задачах, что и Go
По вашей же аналогии я сравниваю седан, трековую машину и пикап, которые втроем приехали на ралийные соревнования
Конечно я буду их сравнивать
ayevdoshenko
23.03.2026 10:55Python и Java - в первую очередь языки для описания сложных доменов - для этого у них и есть соответствующие инструменты, которые позволяют без лишнего скрипа, а пожалуй что - и комфортно, описывать то, для чего они и были созданы. В инфраструктурных задачах они будут проигрывать языкам, заточенных под построение инфраструктуры, как те, в свою очередь, будут вынуждать писать логику какими-то нетривиальными подходами - потому что нет объектов, нет элементарных enum.
Я нигде и не утверждаю, что Python и Java - лучше во всём : ) потому что это элементарно не так, потому что область применения неизбежно диктует наиболее подходящий дизайн языка программирования, и чем более специфицируешься - и получаешь удобные плюшки для ниши, тем менее становишься универсальным. Всё имеет свою цену - и спецификация, и универсальность.
Rust - и вовсе выбор для системного программирования. Да, и на нём можно писать всё остальное - но это, утрируя, как забивать гвозди микроскопом. На каком-то уровне - сойдет; если нужна максимальная эффективность - будешь страдать.
Или вы будете утверждать, что таки Go - это и великолепный швец, и жнец, и на дуде игрец, ибо реализовал "тоталитарность"? : )
Siemargl
23.03.2026 10:55Ява прямой конкурент Го.
Разве что создавалась в эпоху мейнфреймов в серьёзном бизнесе, и несёт многое оттуда.
А Питон это вообще не отсюда. Но его суют везде, с известным результатом.

ayevdoshenko
23.03.2026 10:55Java моложе Python : )
И про мейнфреймы — это, мягко говоря, натяжка. Java создавалась как "write once, run anywhere" для разнородных систем, а не для мейнфреймов. Но это к делу не относится.Суть в другом: и Java, и Python — языки широкого профиля, каждый со своей историей и компромиссами. Go — язык узкой специализации. И это не делает его "хуже" или "лучше". Просто разное назначение. А когда начинают сравнивать специализированный инструмент с универсальным и удивляться, почему у второго якобы "лишние" детали — это странно : )

TeaDove Автор
23.03.2026 10:55Я скорее к тому, что Go де-факто конкурирует с Python, Java и JS в сфере бекенд разработки при этом в одинаковых задачах.
Вот, например, нужно вам написать бекенд для нового интернет магазина, к вам придет CEO и будет докапывать, а что лучше использовать?
И вроде Java и Go по перформансу будут похожи, но в джаве магии больше, из-за чего что-то сложное сделать просто, но зато и что-то простое сделать сложно тоже легко. А в Go наоборот, и сложное и просто делается одинаково просто, но долго. (Грубо говоря)
А еще в дискуссию можно запихнуть Python и JS с TS и тоже сидеть гадать что лучше использовать

krovlads
23.03.2026 10:55Посоветуйте статью от такого же увлечённого товарища о Rust

Tuxman
23.03.2026 10:55Время вкатывания в Go ~ 2 недели, в Rust ~ от 2х месяцев и больше. В мысле, если вы на чём то ещё раньше программировали, а не просто на пианино играли.

DandyDan
23.03.2026 10:55В этой статье рассматриваются плюсы и минусы языка, поэтому про Rust такую статью писать нельзя.
Про Rust можно писать только хорошее. Писать плохое – предательство. Дискредитация системы владения и заимствования – предательство. Сомнения в безопасности – предательство. Паника – это нормально, это для вашей же безопасности.
sse
Golang это "си для кластеров", но рафинированный, чтобы поменьше стрелять себе в ногу -- это понятно, средний уровень разработчика теперь пониже, зато серверов и программистов у гугла много. Си был довольно простым и ограниченным языком для написания одностраничных утилит под стиль unix, Го это тоже самое, только юникс теперь распределенный