What is the idiomatic Go equivalent of C's ternary operator?

GoTernary OperatorConditional Operator

Go Problem Overview


In C/C++ (and many languages of that family), a common idiom to declare and initialize a variable depending on a condition uses the ternary conditional operator :

int index = val > 0 ? val : -val

Go doesn't have the conditional operator. What is the most idiomatic way to implement the same piece of code as above ? I came to the following solution, but it seems quite verbose

var index int

if val > 0 {
    index = val
} else {
    index = -val
}

Is there something better ?

Go Solutions


Solution 1 - Go

As pointed out (and hopefully unsurprisingly), using if+else is indeed the idiomatic way to do conditionals in Go.

In addition to the full blown var+if+else block of code, though, this spelling is also used often:

index := val
if val <= 0 {
    index = -val
}

and if you have a block of code that is repetitive enough, such as the equivalent of int value = a <= b ? a : b, you can create a function to hold it:

func min(a, b int) int {
    if a <= b {
        return a
    }
    return b
}

...

value := min(a, b)

The compiler will inline such simple functions, so it's fast, more clear, and shorter.

Solution 2 - Go

No Go doesn't have a ternary operator, using if/else syntax is the idiomatic way.

> ### Why does Go not have the ?: operator? > There is no ternary testing operation in Go. You may use the following to achieve the same result: > > if expr { > n = trueVal > } else { > n = falseVal > } > > > The reason ?: is absent from Go is that the language's designers had seen the operation used too often to create impenetrably complex expressions. The if-else form, although longer, is unquestionably clearer. A language needs only one conditional control flow construct. > > — Frequently Asked Questions (FAQ) - The Go Programming Language

Solution 3 - Go

Suppose you have the following ternary expression (in C):

int a = test ? 1 : 2;

The idiomatic approach in Go would be to simply use an if block:

var a int

if test {
  a = 1
} else {
  a = 2
}

However, that might not fit your requirements. In my case, I needed an inline expression for a code generation template.

I used an immediately evaluated anonymous function:

a := func() int { if test { return 1 } else { return 2 } }()

This ensures that both branches are not evaluated as well.

Solution 4 - Go

The map ternary is easy to read without parentheses:

c := map[bool]int{true: 1, false: 0} [5 > 4]

Solution 5 - Go

Foreword: Without arguing that if else is the way to go, we can still play with and find pleasure in language-enabled constructs.

Go 1.18 generics update: Go 1.18 adds generics support. It is now possible to create a generic If() function like this. Note: This is available in github.com/icza/gog, as gog.If() (disclosure: I'm the author).

func If[T any](cond bool, vtrue, vfalse T) T {
	if cond {
		return vtrue
	}
	return vfalse
}

Which you can use like this:

min := If(i > 0, i, 0)

The pre-1.18 answer follows:


The following If construct is available in my github.com/icza/gox library with lots of other methods, being the gox.If type.


Go allows to attach methods to any user-defined types, including primitive types such as bool. We can create a custom type having bool as its underlying type, and then with a simple type conversion on the condition, we have access to its methods. Methods that receive and select from the operands.

Something like this:

type If bool

func (c If) Int(a, b int) int {
	if c {
		return a
	}
	return b
}

How can we use it?

i := If(condition).Int(val1, val2)  // Short variable declaration, i is of type int
     |-----------|  \
   type conversion   \---method call

For example a ternary doing max():

i := If(a > b).Int(a, b)

A ternary doing abs():

i := If(a >= 0).Int(a, -a)

This looks cool, it's simple, elegant, and efficient (it's also eligible for inlining).

One downside compared to a "real" ternary operator: it always evaluates all operands.

To achieve deferred and only-if-needed evaluation, the only option is to use functions (either declared functions or methods, or function literals), which are only called when / if needed:

func (c If) Fint(fa, fb func() int) int {
	if c {
		return fa()
	}
	return fb()
}

Using it: Let's assume we have these functions to calculate a and b:

func calca() int { return 3 }
func calcb() int { return 4 }

Then:

i := If(someCondition).Fint(calca, calcb)

For example, the condition being current year > 2020:

i := If(time.Now().Year() > 2020).Fint(calca, calcb)

If we want to use function literals:

i := If(time.Now().Year() > 2020).Fint(
	func() int { return 3 },
	func() int { return 4 },
)

Final note: if you would have functions with different signatures, you could not use them here. In that case you may use a function literal with matching signature to make them still applicable.

For example if calca() and calcb() would have parameters too (besides the return value):

func calca2(x int) int { return 3 }
func calcb2(x int) int { return 4 }

This is how you could use them:

i := If(time.Now().Year() > 2020).Fint(
	func() int { return calca2(0) },
	func() int { return calcb2(0) },
)

Try these examples on the Go Playground.

Solution 6 - Go

func Ternary(statement bool, a, b interface{}) interface{} {
    if statement {
        return a
    }
    return b
}

func Abs(n int) int {
    return Ternary(n >= 0, n, -n).(int)
}

This will not outperform if/else and requires cast but works. FYI:

BenchmarkAbsTernary-8 100000000 18.8 ns/op

BenchmarkAbsIfElse-8 2000000000 0.27 ns/op

Solution 7 - Go

If all your branches make side-effects or are computationally expensive the following would a semantically-preserving refactoring:

index := func() int {
	if val > 0 {
		return printPositiveAndReturn(val)
	} else {
		return slowlyReturn(-val)  // or slowlyNegate(val)
	}
}();  # exactly one branch will be evaluated

with normally no overhead (inlined) and, most importantly, without cluttering your namespace with a helper functions that are only used once (which hampers readability and maintenance). Live Example

Note if you were to naively apply Gustavo's approach:

    index := printPositiveAndReturn(val);
	if val <= 0 {
		index = slowlyReturn(-val);  // or slowlyNegate(val)
	}

you'd get a program with a different behavior; in case val <= 0 program would print a non-positive value while it should not! (Analogously, if you reversed the branches, you would introduce overhead by calling a slow function unnecessarily.)

Solution 8 - Go

One-liners, though shunned by the creators, have their place.

This one solves the lazy evaluation problem by letting you, optionally, pass functions to be evaluated if necessary:

func FullTernary(e bool, a, b interface{}) interface{} {
	if e {
		if reflect.TypeOf(a).Kind() == reflect.Func {
			return a.(func() interface{})()
		}
		return a
	}
	if reflect.TypeOf(b).Kind() == reflect.Func {
		return b.(func() interface{})()
	}
	return b
}

func demo() {
	a := "hello"
	b := func() interface{} { return a + " world" }
	c := func() interface{} { return func() string { return "bye" } }
	fmt.Println(FullTernary(true, a, b).(string)) // cast shown, but not required
	fmt.Println(FullTernary(false, a, b))
	fmt.Println(FullTernary(true, b, a))
	fmt.Println(FullTernary(false, b, a))
	fmt.Println(FullTernary(true, c, nil).(func() string)())
}

Output

hello
hello world
hello world
hello
bye
  • Functions passed in must return an interface{} to satisfy the internal cast operation.
  • Depending on the context, you might choose to cast the output to a specific type.
  • If you wanted to return a function from this, you would need to wrap it as shown with c.

The standalone solution here is also nice, but could be less clear for some uses.

Solution 9 - Go

As others have noted, golang does not have a ternary operator or any equivalent. This is a deliberate decision thought to improve readability.

This recently lead me to a scenario where constructing a bit-mask in a very efficient manner became hard to read when written idiomatically, or very inefficient when encapsulated as a function, or both, as the code produces branches:

package lib

func maskIfTrue(mask uint64, predicate bool) uint64 {
  if predicate {
    return mask
  }
  return 0
}

producing:

        text    "".maskIfTrue(SB), NOSPLIT|ABIInternal, $0-24
        funcdata        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        funcdata        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        movblzx "".predicate+16(SP), AX
        testb   AL, AL
        jeq     maskIfTrue_pc20
        movq    "".mask+8(SP), AX
        movq    AX, "".~r2+24(SP)
        ret
maskIfTrue_pc20:
        movq    $0, "".~r2+24(SP)
        ret

What I learned from this was to leverage a little more Go; using a named result in the function (result int) saves me a line declaring it in the function (and you can do the same with captures), but the compiler also recognizes this idiom (only assign a value IF) and replaces it - if possible - with a conditional instruction.

func zeroOrOne(predicate bool) (result int) {
  if predicate {
    result = 1
  }
  return
}

producing a branch-free result:

    movblzx "".predicate+8(SP), AX
    movq    AX, "".result+16(SP)
    ret

which go then freely inlines.

package lib

func zeroOrOne(predicate bool) (result int) {
  if predicate {
    result = 1
  }
  return
}

type Vendor1 struct {
    Property1 int
    Property2 float32
    Property3 bool
}

// Vendor2 bit positions.
const (
    Property1Bit = 2
    Property2Bit = 3
    Property3Bit = 5
)

func Convert1To2(v1 Vendor1) (result int) {
    result |= zeroOrOne(v1.Property1 == 1) << Property1Bit
    result |= zeroOrOne(v1.Property2 < 0.0) << Property2Bit
    result |= zeroOrOne(v1.Property3) << Property3Bit
    return
}

produces https://go.godbolt.org/z/eKbK17

    movq    "".v1+8(SP), AX
    cmpq    AX, $1
    seteq   AL
    xorps   X0, X0
    movss   "".v1+16(SP), X1
    ucomiss X1, X0
    sethi   CL
    movblzx AL, AX
    shlq    $2, AX
    movblzx CL, CX
    shlq    $3, CX
    orq     CX, AX
    movblzx "".v1+20(SP), CX
    shlq    $5, CX
    orq     AX, CX
    movq    CX, "".result+24(SP)
    ret

Solution 10 - Go

eold's answer is interesting and creative, perhaps even clever.

However, it would be recommended to instead do:

var index int
if val > 0 {
    index = printPositiveAndReturn(val)
} else {
    index = slowlyReturn(-val)  // or slowlyNegate(val)
}

Yes, they both compile down to essentially the same assembly, however this code is much more legible than calling an anonymous function just to return a value that could have been written to the variable in the first place.

Basically, simple and clear code is better than creative code.

Additionally, any code using a map literal is not a good idea, because maps are not lightweight at all in Go. Since Go 1.3, random iteration order for small maps is guaranteed, and to enforce this, it's gotten quite a bit less efficient memory-wise for small maps.

As a result, making and removing numerous small maps is both space-consuming and time-consuming. I had a piece of code that used a small map (two or three keys, are likely, but common use case was only one entry) But the code was dog slow. We're talking at least 3 orders of magnitude slower than the same code rewritten to use a dual slice key[index]=>data[index] map. And likely was more. As some operations that were previously taking a couple of minutes to run, started completing in milliseconds.\

Solution 11 - Go

I have compiled some items and compared the speed.

/*
go test ternary_op_test.go -v -bench="^BenchmarkTernaryOperator" -run=none -benchmem
*/
package _test

import (
	"testing"
)

func BenchmarkTernaryOperatorIfElse(b *testing.B) {
	for i := 0; i < b.N; i++ {
		if i%2 == 0 {
			_ = i
		} else {
			_ = -i
		}
	}
}

// https://stackoverflow.com/a/45886594/9935654
func Ternary(statement bool, a, b interface{}) interface{} {
	if statement {
		return a
	}
	return b
}

func BenchmarkTernaryOperatorTernaryFunc(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = Ternary(i%2 == 0, i, -i).(int)
	}
}

// https://stackoverflow.com/a/34636594/9935654
func BenchmarkTernaryOperatorWithFunc(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = func() int {
			if i%2 == 0 {
				return i
			} else {
				return -i
			}
		}
	}
}

// https://stackoverflow.com/a/31483763/9935654
func BenchmarkTernaryOperatorMap(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = map[bool]int{true: i, false: -i}[i%2 == 0]
	}
}

output

goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
BenchmarkTernaryOperatorIfElse
BenchmarkTernaryOperatorIfElse-8                1000000000               0.4460 ns/op          0 B/op          0 allocs/op
BenchmarkTernaryOperatorTernaryFunc
BenchmarkTernaryOperatorTernaryFunc-8           1000000000               0.3602 ns/op          0 B/op          0 allocs/op
BenchmarkTernaryOperatorWithFunc
BenchmarkTernaryOperatorWithFunc-8              659517496                1.642 ns/op           0 B/op          0 allocs/op
BenchmarkTernaryOperatorMap
BenchmarkTernaryOperatorMap-8                   13429532                82.48 ns/op            0 B/op          0 allocs/op
PASS
ok      command-line-arguments  4.365s

Solution 12 - Go

One more suggestion for the idiomatic approach in Go of ternary operator:

package main

import (
	"fmt"
)

func main() {
	val := -5

	index := func (test bool, n, d int) int {
		if test {
			return n
		}
		return d
	}(val > 0, val, -val)
	
	fmt.Println(index)
}

Go Playground

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFabienView Question on Stackoverflow
Solution 1 - GoGustavo NiemeyerView Answer on Stackoverflow
Solution 2 - GoishaaqView Answer on Stackoverflow
Solution 3 - GoPeter BoyerView Answer on Stackoverflow
Solution 4 - Gouser1212212View Answer on Stackoverflow
Solution 5 - GoiczaView Answer on Stackoverflow
Solution 6 - GoPhillip DominyView Answer on Stackoverflow
Solution 7 - GoeoldView Answer on Stackoverflow
Solution 8 - GoBrent BradburnView Answer on Stackoverflow
Solution 9 - GokfsoneView Answer on Stackoverflow
Solution 10 - GoCassy FoeschView Answer on Stackoverflow
Solution 11 - GoCarsonView Answer on Stackoverflow
Solution 12 - GohdiasView Answer on Stackoverflow