Menu

Тернарный оператор в Golang: почему его нет и чем заменить

В Go нет тернарного оператора (cond ? a : b). Здесь объясняется почему, показана идиоматичная замена через if/else, cmp.Or для значений по умолчанию и обобщённая функция-помощник вместе с ловушкой, которая отличает её от настоящего тернарного оператора.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

В Go нет тернарного оператора. Никакого cond ? a : b. Идиоматичная замена это инструкция if:

Объявите переменную со значением по умолчанию, а затем измените её в if. Когда работа нужна в обеих ветках, используйте if/else:

var fee int
if member {
	fee = 0
} else {
	fee = computeFee(order)
}

Почему в Go его нет

Go FAQ отвечает на это прямо: создатели слишком часто видели, как оператор ?: используют для построения непроницаемо сложных выражений, и решили, что if/else, хоть и длиннее, несомненно понятнее. Языку нужна только одна конструкция условного управления.

На практике отсутствие оператора стоит двух-трёх строк на каждое использование и убирает целую категорию вложенных выражений вроде a ? b ? c : d : e.

Ранний возврат

Внутри функции самая чистая форма часто обходится вообще без переменной:

Вынести условие в маленькую именованную функцию часто лучшее решение: место вызова читается как тот самый тернарный оператор, который вам хотелось, а имя функции документирует смысл условия.

Значения по умолчанию через cmp.Or (Go 1.22)

Самый частый тернарный оператор в других языках это «используй это значение или запасное, если оно пустое»: name ? name : "anonymous" или name || "anonymous" в JavaScript. Ровно для этого в Go 1.22 появился cmp.Or. Он возвращает первый аргумент, который не равен нулевому значению своего типа:

Нулевое значение он считает «отсутствующим», поэтому не отличает «порт равен 0 намеренно» от «порт не задан». Когда ноль допустимое значение, используйте указатель, отдельный bool или явный if.

Обобщённый помощник If и его ловушка

С дженериками (Go 1.18) можно написать функцию, которая выглядит как тернарный оператор:

func If[T any](cond bool, a, b T) T {
	if cond {
		return a
	}
	return b
}

status := If(ok, "pass", "fail")

Для простых значений она работает, и в некоторых кодовых базах её используют. Но это не тернарный оператор, и разница важна. Go вычисляет все аргументы до вызова функции, поэтому a и b вычисляются всегда:

Первый вызов вычисляет и "a", и "b", а второй паникует с разыменованием nil-указателя, хотя условие ложно. Настоящий тернарный оператор вычислил бы только выбранную сторону. Поэтому:

  • Никогда не используйте такой помощник, когда одна из сторон разыменовывает указатель, обращается к слайсу по индексу или читает элемент мапы, которого может не быть.
  • Никогда не используйте его, когда одна из сторон дорогая или имеет побочные эффекты.
  • Для дешёвых и безопасных значений он работает, но многие ревьюеры Go всё равно попросят обычный if.

Ветки можно сделать ленивыми, передавая функции, If(ok, func() string { return a }, func() string { return b }), но тогда инструкция if уже короче.

Другие трюки и почему их лучше избегать

Поиск в мапе. map[bool]string{true: "yes", false: "no"}[ok] работает, при каждом вычислении строит мапу и хеширует ключ, а также вычисляет оба значения. Это курьёз, а не идиома.

Немедленно вызываемая функция. Анонимная функция, вызванная на месте, даёт выражение:

label := func() string {
	if n > 0 {
		return "positive"
	}
	return "non-positive"
}()

Здесь вычисление действительно ленивое, и иногда это помогает внутри составного литерала, куда инструкцию не поставить. Но чаще понятнее вычислить значение в переменную прямо перед литералом.

Чем заменить

СитуацияЧто использовать
Выбор между двумя значениямизначение по умолчанию, затем if
Обе ветки требуют вычисленийif/else
Значение определяет результат функцииранний return
Запасное значение, если значение пустоеcmp.Or(value, fallback)
Три и больше вариантовswitch
Повторяющееся условие со смысломмаленькая именованная функция

Предложения добавить условное выражение регулярно появляются в трекере задач Go и пока отклоняются. Пишите if.

Часто задаваемые вопросы

Есть ли в Go тернарный оператор?

Нет. Выражения cond ? a : b в Go нет. В Go FAQ сказано, что создатели не включили его, потому что им слишком часто строят трудночитаемые выражения. Используйте инструкцию if/else.

Как написать условное присваивание в одну строку в Go?

Сначала присвойте значение по умолчанию, затем переопределите его:

label := "odd"
if n%2 == 0 {
	label = "even"
}

Это и есть идиоматичная замена. gofmt не оставит тело if на одной строке, так что это всегда минимум три строки.

Можно ли написать обобщённую тернарную функцию в Go?

Да: func If[T any](cond bool, a, b T) T { if cond { return a }; return b }. Подвох в том, что Go вычисляет все аргументы до вызова функции, поэтому вычисляются и a, и b. If(p != nil, p.Name, "") всё равно разыменует nil p и запаникует. Настоящий тернарный оператор вычислил бы только одну сторону.

Как задать значение по умолчанию, если строка пустая, в Go?

Начиная с Go 1.22 cmp.Or(name, "anonymous") возвращает первый аргумент, не равный нулевому значению, то есть даёт name, если только name не равно "". Функция работает с любым сравнимым типом и, как любой вызов функции, вычисляет все свои аргументы.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ