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 ?
812 Answers
Answer recommended by Go Language
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.
15No Go doesn't have a ternary operator, using if/else syntax is the idiomatic way.
9Why 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. Theif-elseform, although longer, is unquestionably clearer. A language needs only one conditional control flow construct.— Frequently Asked Questions (FAQ) - The Go Programming Language
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.
5The map ternary is easy to read without parentheses:
c := map[bool]int{true: 1, false: 0} [5 > 4] 11Foreword: 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 , 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 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.
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
2If 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.)
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.
2As 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 } 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 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.\
3I 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 } } } // 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) } } // func BenchmarkTernaryOperatorWithFunc(b *testing.B) { for i := 0; i < b.N; i++ { _ = func() int { if i%2 == 0 { return i } else { return -i } } } } // 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 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) }