Первое же, чем я отстрелил себе ногу в го - отсутствие каких-либо компайл-тайм гарантий относительно гонок. Всё как в старом добром си - что-то меняешь конкурентно, будь добр взять мьютекс. Забыл - сам себе злобный буратино. Отсутствие константности в го усугубляет проблему.
Второе, чем я себе отстрелил ногу уже три раза - это утечки. В го нет raii, так что забыл позвать defer close(), например, на grpc клиент - опять сам себе буратино. Парадоксально, что GC не спасает от утечек :)
Третье, на что я напарывался уже пару раз в проде - это дурацкий захват переменной в цикле. Типа хочешь создать 1000 каких-то объектов через grpc, пишешь без задней мысли Name: &obj.name
(где obj - переменная цикла), и получаешь запрос на создание 1000 объектов с одинаковым именем. Можно сказать, что сам дурак, но вне цикла & это по сути аллокация объекта на куче, так что само напрашивается. Здесь borrow checker без GC сработал бы намного лучше - у меня бы не скомпилился код, где я вместо аллокации на куче случайно захватил локальную переменную. GC же тут только мешает, делая не то, что я ожидаю.
В-четвёртых, кмк парадигма "любой объект можно инициализировать нуллом" неудачная. Например, хочу написать тип
type Foo struct {
inner SomeIface
}
Мне в этом типе надо во всех методах проверять, что inner не nil (ведь клиент может написать просто var Foo foo), чтобы хотя бы внятную ошибку дать (типа "используйте NewFoo для конструирования"). Или забить, тогда будет паника с nil dereference, причина которой может быть клиенту неочевидна.
Это основное, по мелочи ещё тоже есть. Например, по типу не понятно, это struct или interface (от этого зависит nullability). Про nullability ещё на нравится, что очень много типов могут быть null (указатели, интерфейсы, мапы, слайсы). Намного лучше кмк, когда типы не nullable, но есть option. Банально не понятно, если функция возвращает nullable тип - стоит ли проверить на null или нет? Я обычно стараюсь проверять, так что возникает много бойлерплейта