Почему в Go возвращается слайс, а не один элемент
Что проверяют
Умеете ли вы выбирать сигнатуру под кардинальность результата и понимаете ли, что слайс в Go это дешёвый заголовок, а не контейнер с данными. Вопрос про проектирование API, а не про синтаксис.
Как отвечать
Слайс возвращают, когда результатов может быть ноль, один или много, и заранее это неизвестно. Тип сразу выражает такую неопределённость, и вызывающему не нужен отдельный признак «не нашли». Нулевой слайс равен nil, но с ним корректно работают len, range и append, поэтому пустой результат обрабатывается тем же кодом, что и непустой.
Если же результат ровно один, слайс наоборот вреден: он навязывает проверку длины и обращение по индексу там, где хватило бы понятной сигнатуры. В стандартной библиотеке идиомы разделены именно так:
(T, error)там, где значение обязано быть одно, а иначе это ошибка;(T, bool)для поиска, как при чтении из карты;[]Tдля выборки, как уstrings.Splitилиregexp.FindAllString.
Про стоимость: заголовок слайса это указатель на массив, длина и ёмкость, то есть три машинных слова. Он копируется одинаково дёшево при любом объёме данных, тогда как массив фиксированной длины копируется целиком при каждой передаче. Поэтому массивы в сигнатурах почти не встречаются.
Где спотыкаются
Возвращают срез чужого массива и не думают о владении. Вызывающий получает вид на память, которую функция ещё использует, а append к укороченному срезу перезаписывает соседние элементы оригинала. Полное выражение среза с ограничением ёмкости или честная копия закрывают вопрос.
Второе: считают пустой результат ошибкой. Функция возвращает error там, где просто ничего не нашлось, и вызывающему приходится отличать «пусто» от «сломалось» разбором текста ошибки. Пустая выборка это нормальный ответ.
Третье: забывают про сериализацию. Нулевой слайс уходит в JSON как null, а инициализированный пустой как пустой массив. Клиент, ожидающий массив, падает на первом же переборе, поэтому в ответах API слайс создают через make.