559

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 ?

lospejos
  • 1,976
  • 3
  • 19
  • 35
Fabien
  • 12,486
  • 9
  • 44
  • 62
  • you could initialize the value with the else part and only check for your condition to change, not sure it thats better though – x29a Nov 14 '13 at 13:45
  • A lot of if/thens should have been eliminated anyway. We used to do this all the time from the days I wrote my first BASIC programs 35 years ago. Your example could be: `int index = -val + 2 * val * (val > 0);` – hyc Sep 12 '14 at 18:25
  • 16
    @hyc your example is far from being as readable as go's idiomatic code, or even as C's version using the ternary operator. Anyway, AFAIK, it is not possible to implement this solution in Go as a boolean cannot be used as a numeric value. – Fabien Sep 13 '14 at 23:24
  • 3
    Wondering why go didn't provide such an operator? – Eric Jul 31 '18 at 05:51
  • @EricWang Two reasons, AFAIK: 1- you don't need it, and they wanted to keep the language as small as possible. 2- it tends to be abused, i.e used in convoluted multiple-line expressions, and the language designers don't like it. – Fabien Aug 01 '18 at 16:41
  • 3
    Everything in @Fabien's answer except the last few words is flawed logic. If you don't need ternary then you don't need switch, yet they included that, so clearly that isn't a similarly considered answer. It tends to be abused less than complicated `if`-statement conditionals, so it doesn't make sense that it would be that. The designers don't like it -- that sounds more probable. Some developers poorly formatting their code or using parentheses should not disqualify useful language features, especially when `gofmt` is required and can do the work. – user1775138 Dec 24 '19 at 18:12
  • 2
    Probably go should & would add the `ternary operator` in future. – Eric Dec 27 '20 at 10:06
  • 1
    If I remember correctly from reading the github issues, the ternary operator isn't included because it can't (or is too messy to) be parsed by Go's single-pass compiler. – Ronald Currier Feb 06 '22 at 06:51

15 Answers15

455

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.

Gustavo Niemeyer
  • 22,007
  • 5
  • 57
  • 46
  • 278
    Hey guys, look! I just ported the _ternarity_ operator to the golangs! http://play.golang.org/p/ZgLwC_DHm0. _So_ efficient! – thwd Nov 14 '13 at 14:35
  • 1
    @tomwilde interesting, but doesn't exactly flow off the tongue. Also, it would seem using a map instead of conditionals is less efficient. I will say your solution is creative though! – Brenden Nov 14 '13 at 16:49
  • 45
    @tomwilde your solution looks pretty interesting, but it lacks one of the main features of ternary operator - conditional evaluation. – Vladimir Matveev Nov 14 '13 at 18:13
  • 18
    @VladimirMatveev wrap the values in closures ;) – nemo Nov 14 '13 at 20:03
  • 3
    I guess you can discuss if `if/else` is more clear, but how is it shorter? – Thomas Ahle Jun 28 '14 at 22:21
  • 92
    `c := (map[bool]int{true: a, false: a - 1})[a > b]` is an example of obfuscation IMHO, even if it works. – Rick-777 Feb 28 '15 at 12:39
  • 67
    If `if/else` is the idiomatic approach then perhaps Golang could consider letting `if/else` clauses return a value: `x = if a {1} else {0}`. Go would be by no means the only language to work this way. A mainstream example is Scala. See: http://alvinalexander.com/scala/scala-ternary-operator-syntax – Max Murphy Aug 04 '16 at 13:04
  • 2
    Yea I was gonna say the same thing @MaxMurphy. These golangers don't know what they're missing! The disadvantage to the current "idiomatic way" is that you can't define constants that way – U Avalos Oct 05 '16 at 19:50
  • 7
    The map solution does not seem to work properly, as it calculates both branches, whereas the real `?:` operator should calculate only the branch applicable for the condition. – Andrevinsky Mar 28 '17 at 16:32
  • Ok, all the benefits of golang and the fun it is to code. BUT NO ELVIS! Going back to JavaScript! lol, j/k – Arnaldo Capo Dec 02 '17 at 12:33
  • 3
    It is too long and looks complicated. I use ternary operator for simplicity – tom10271 Dec 11 '17 at 07:04
  • 1
    golang code like this reminds me of how nice the language is and how it mirrors ways I've written nodejs and JavaScript for years. Thanks for the clean answer! – mattdlockyer Dec 13 '18 at 16:48
  • Dude. Clever? Yes. Takes time to figure out the intention if never seen before? Yes. Would I like to see that code at 3am if I'm called in an emergency? No. This is not clean and I don't advocate cleverness over the KISS principle. – KRK Owner Mar 04 '19 at 23:36
  • 2
    Downvoted because the solution is not equivalent to C ternary. The code block provided here _cannot_ be used as an expression. An immediately evaluated closure does. – C.W. Nov 03 '20 at 18:12
  • The problem with if/else is that I have to have a variable and I need to declare the type of it. I can't do `callSomeFunc(if someCondition {34} else {42})`. And then not worry about `callSomeFunc`'s argument type. – Peter V. Mørch May 26 '22 at 11:22
166

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

Hymns For Disco
  • 7,530
  • 2
  • 17
  • 33
ishaaq
  • 6,329
  • 3
  • 16
  • 28
  • 168
    So just because what the language designers have seen, they have omitted a one-liner for a whole `if-else` block? And who says `if-else` isn't abused in like manner? I'm not attacking you, I just feel that the excuse by the designers isn't valid enough – Alf Moh Jun 09 '20 at 11:42
  • 48
    I agree. Ugly ternarys are a coding problem, not a language problem. Ternarys are common enough across languages that they are normal and not having them is a surprise, which violates POLA/PLA if you ask me. – jcollum Jun 24 '20 at 17:41
  • 4
    But think of it from the language designer's perspective; they need to extend the language specification, parser, compiler, etc with extra syntax that isn't used anywhere else in the language for some syntactic sugar that is a potential readability footgun. Go is designed for reading, and while most C-developers may be familiar enough with ternaries to be able to read them quickly enough, this is not a universal truth, and things go really south when people start nesting them. "This other language has it" is NOT a valid argument to add a language feature. – cthulhu Aug 13 '20 at 14:47
  • 3
    @cthulhu If that is their concern, messy conditionals... I wonder if they could at least only allow that ternary to operate as one operation, ie. just return the value of the second argument, but don't execute it (don't recurse further into the next tree of operations)... ie: x = a ?: b // use b if a is falsey ... would only return a or b, but not evaluate them further... but I'm not sure if that would break parsing rules. I don't think the operator is confusing and usually only has this intention, which should be readable enough in and of itself, I think. – Ryan Weiss Oct 18 '20 at 23:44
  • 10
    The explanation from the language's designers looks weird because it contradicts another language feature: if including 2 statements separated by semicolon (see https://tour.golang.org/flowcontrol/6). I doubt that the second one makes the code clear. They could have implemented ternary if with limitation of just one '?' per statement. – Yury Kozlov Mar 11 '21 at 19:15
  • 1
    safety scissors – Sam May 13 '21 at 23:53
  • Maybe there's a way to allow only one ternary operation in one line? – Olivier Pons Dec 06 '21 at 18:57
  • @cthulu - that's thinking about it from the perspective of the language designers _decision_, not the same thing. There is nothing to say that golang would have to use C style ternary operators. They could instead use the Scala/Oxygene approach of allowing "if" statements to be used as expressions. No new language syntax, no special case syntax, simply [sic] an extension of an existing language construct in an intuitive and expressive manner: int result = if a > b {a} else {b}. The only "specialism" would be that if expressions MUST have an else (Oxygene also allow case expressions). – Deltics Dec 13 '21 at 06:46
  • 8
    "A language needs only one conditional control flow construct." - Sure, but why not make `if` an expression and allow constructs like `if cond { value1 } else { value3 } like Rust or Scala? –  Dec 30 '21 at 16:44
  • @Alf Moh man I don't get it. You can have this iota, that is fragile (merging), badly readable considered idiomatic while this ternary operator banned as evil. – ooouuiii Nov 23 '22 at 11:27
  • Pedantic language designers, as usual. Contemptuous to developers, considering them stupid, and at the end creating a quite unusable language, with 1% of TIOBE ranking that means => One enterprise may attempt to develop one project with _Go_ one time. But never twice. One burned hand is enough. – Marc Le Bihan Jun 28 '23 at 08:49
86

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.

Peter Boyer
  • 1,037
  • 9
  • 5
  • Good to know that only one branch of the inlined anon function gets evaluated. But note that cases like this are beyond the scope of C's ternary operator. – Wolf Dec 12 '16 at 10:34
  • 4
    The C conditional expression (commonly known as the ternary operator) has three operands: `expr1 ? expr2 : expr3`. If `expr1` evaluates to `true`, `expr2` is evaluated and is the result of the expression. Otherwise, `expr3` is evaluated and provided as the result. This is from the ANSI C Programming Language section 2.11 by K&R. My Go solution preserves these specific semantics. @Wolf Can you clarify what you are suggesting? – Peter Boyer May 31 '17 at 01:26
  • I'm not sure what I had in mind, maybe that anon functions provide a scope (local namespace) which is not the case with the ternary operator in C/C++. See an [example for using this scope](https://play.golang.org/p/VEv1PHVCJh) – Wolf May 31 '17 at 12:09
  • 3
    "simply" in this case looks more like "complicatedly" – micahhoover Dec 10 '20 at 19:50
  • 8
    Why add "else"? `a := func() int { if test { return 1 } return 2 }()` should work or am I wrong? – Olivier Pons Apr 15 '21 at 05:12
64

The map ternary is easy to read without parentheses:

c := map[bool]int{true: 1, false: 0} [5 > 4]
user1212212
  • 1,311
  • 10
  • 6
  • 2
    Not entirely sure why it has got -2 ... yes, it is a workaround but it works and is type-safe. – Alessandro Santini Aug 03 '15 at 11:52
  • 52
    Yes, it works, is type-safe, and is even creative; however, there are other metrics. Ternary ops are runtime equivalent to if/else (see e.g. [this S/O post](http://stackoverflow.com/questions/3565368/ternary-operator-vs-if-else)). This response is not because 1) both branches are executed, 2) creates a map 3) calls a hash. All of these are "fast", but not as fast as an if/else. Also, I would argue that it's not more readable than var r T if condition { r = foo() } else { r = bar() } – knight Dec 01 '15 at 20:55
  • In other languages I use this approach when I have multiple variables and with closures or function pointers or jumps. Writing nested ifs becomes error prone as the number of variables increases, whereas e.g. {(0,0,0) => {code1}, (0,0,1) => {code2} ...}[(x>1,y>1,z>1)] (pseudocode) becomes more and more attractive as the number of variables goes up. The closures keep this model fast. I expect that similar tradeoffs apply in go. – Max Murphy Jun 29 '16 at 11:10
  • I suppose in go you would use a switch for that model. I love the way go switches break automatically, even if it is occasionally inconvenient. – Max Murphy Jun 29 '16 at 11:14
  • 15
    as [Cassy Foesch pointed out:](http://stackoverflow.com/a/37199664/2932052) *`simple and clear code is better than creative code.`* – Wolf Dec 13 '16 at 07:52
  • Hello, nice synthax but every potential results are analysed even if they are not triggered. So if you have : (map[bool]string{true: referenceTab[i], false: newTab[i]})[newTab == nil], the true condition will throw an error : Out of Range... – Mech45 Jul 10 '18 at 15:25
  • This is NOT easy to read, and it is more code and more expensive than an if / else. Clear is better than clever. – cthulhu Aug 13 '20 at 14:51
  • "easy to read" is contradicted by the number of things you are explicitly doing in this one line of code (declaration of a non-const map, calculation of a parameter, map lookup of the parameter, assignment) and the insane number of things you are doing implicitly: - inline construction of a map object, - inline population of the map object (hashing true and false, allocating two nodes for them, assigning that to the map), - production of bool key, - hashing of bool key, - binary search of map, - release of 2 map nodes, - release of map, assignment. https://go.godbolt.org/z/Yjb89j – kfsone Sep 08 '20 at 21:59
  • The map appears static in this instance. If it was a global var, then it wouldn't be an alloc to use: c := boolToInt[5>4] – user1212212 Sep 10 '20 at 15:00
  • A comment in accepted answer, by @thwd, mentions this answer, before this was posted.... – kavadias May 20 '21 at 12:17
  • 3
    @Wolf Can you write `fmt.Println("Operation %s; reverting to normal form.", (map[bool]string{true: "skipped", false: "failed"})[opkip])` in a simpler way? – kavadias May 20 '21 at 12:30
61

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.

icza
  • 389,944
  • 63
  • 907
  • 827
  • 2
    Note that the generic `If` doesn't work with nil checks with dereference, like `If(p != nil, *p, defaultValue)` because the arguments, including `*p`, are evaluated right away. – blackgreen Dec 02 '22 at 13:38
  • @blackgreen Yes, this is true for all solutions except when a function is passed that defers the evaluation. – icza Dec 02 '22 at 13:41
22
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

Phillip Dominy
  • 237
  • 2
  • 5
  • 4
    I don't think this handles conditional evaluation, or does it? With side-effect free branches this doesn't matter (like in your example), but if it's something with side-effects you'll run into problems. – Ashton Wiersdorf Sep 26 '19 at 12:34
  • Yes, actually from what Ashton said, it does indeed not offer the conditional evaluation. So in other cases, one could just write `test = function1(); if condition {test = function2()}`, which would be the same and would require no type assertion (faster). In the case of the answer where there is a return involved, no idea. Also depends on if both evaluations or at least the 2nd is very expensive or not. Still thank you for the answer! Seems a good idea despite this. – Edw590 Nov 03 '21 at 23:30
7

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
kfsone
  • 23,617
  • 2
  • 42
  • 74
6

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.)

Community
  • 1
  • 1
eold
  • 5,972
  • 11
  • 56
  • 75
  • 1
    Interesting read, but I'm not really understanding the point in your criticism of Gustavo's approach. I see a (kind of) `abs` function in the original code (well, I'd change `<=` to `<`). In your example I see an initialisation, that is redundant in some case and could be expansive. Can you please clarify: explain your idea a bit more? – Wolf Dec 12 '16 at 10:29
  • The prime difference is that calling a function *outside* of either branch will make side effects *even if that* branch should not have been taken. In my case, only positive numbers will be printed because the function `printPositiveAndReturn` is only called for positive numbers. Conversely, always executing one branch, then "fixing" the value with executing a different branch **does not undo first branch's side effects**. – eold Dec 13 '16 at 00:27
  • I see, but experiences programmers are normally aware of side effects. In that case I'd prefer [Cassy Foesch's obvious solution](http://stackoverflow.com/a/37199664/2932052) to a embedded function, even if the compiled code may be the same: it's shorter and looks obvious to most programmers. Don't get me wrong: I really *love* Go's closures ;) – Wolf Dec 13 '16 at 07:12
  • 1
    "_experiences programmers are normally aware of side effects_" - No. Avoiding the evaluation of terms is one of the primary characteristics of a ternary operator. – Jonathan Hartley Sep 14 '18 at 16:12
5

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.

Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
  • Even if this is definitely not academic, this is pretty nice. – Fabien Jan 28 '20 at 13:09
  • 1
    Hey! You don't actually need the reflect package in there. Plus, Go inlines helper functions pretty aggressively in the compiled binary, so subroutine calls end up basically free...and binaries are surprisingly big. The following might be bit more readable: https://play.golang.org/p/9z1GoskyKLL – Sam Hughes Jun 23 '21 at 14:15
4

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.\

  • 1
    `simple and clear code is better than creative code` - this I like very much, but I'm getting a little confused in the last section after *`dog slow`*, maybe this could be confusing to others too? – Wolf Dec 12 '16 at 10:42
  • 1
    So, basically... I had some code that was creating small maps with one, two, or three entries, but the code was running very slowly. So, a lot of `m := map[string]interface{} { a: 42, b: "stuff" }`, and then in another function iterating through it: `for key, val := range m { code here }` After switching to a two slice system: `keys = []string{ "a", "b" }, data = []interface{}{ 42, "stuff" }`, and then iterate through like `for i, key := range keys { val := data[i] ; code here }` things sped up 1000 fold. – Cassy Foesch Dec 13 '16 at 17:33
  • I see, thanks for the clarification. (Maybe the answer itself *could* be improved in this point.) – Wolf Dec 13 '16 at 17:57
4

Now with the release of go1.18 generics, it's very easy to do it with a generic function like this, and it is reusable through your whole app

package main

import (
    "fmt"
)

func Ternary[T any](condition bool, If, Else T) T {
    if condition {
        return If
    }
    return Else
}

func main() {
    fmt.Println(Ternary(1 < 2, "yes", "no")) // yes
    fmt.Println(Ternary(1 < 2, 1, 0)) // 1
    fmt.Println(Ternary[bool](1 < 2, true, false)) // true
}


be aware if you use it in this case it will crash. in this case, just use an if statement, (because you passing into the function a nil pointer VS an if statement is not calling that section if it is false)


var a *string
fmt.Println(Ternary(a != nil, *a, "some thing else"))

the solution call it with a function, so it will not be excuted if it's false

func TernaryPointer[T any](condition bool, If, Else func() T) T {
    if condition {
        return If()
    }
    return Else()
}
var pString *string
fmt.Println(TernaryPointer(
    pString != nil, // condition 
    func() string { return *pString }, // true
    func() string { return "new data" }, // false
))

but in this case, I think a regular if statement is cleaner (except if go adds arrow functions in the future)

playground

give credit for this answer he already answered it

Isaac Weingarten
  • 977
  • 10
  • 15
3

With generics now being there, one can make this silly function...

func ternary[RV any](cmp bool, rvt RV) RV {
    if cmp {
        return rvt
    }
    var rvf RV
    return rvf
}

...which then can be used this way...

    for i, data := range trials {
        ps.Pages = append(ps.Pages, PDFPage{
            Content:   data,
            ClassName: ternary(i > 0, "pagebreak"),
        })
    }

Possibly harnessing the power of generics in a wrong way D:

Julien Duris
  • 119
  • 5
2

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
Carson
  • 6,105
  • 2
  • 37
  • 45
  • Thankyou for some real data. However I'm a bit worried when `BenchmarkTernaryOperatorTernaryFunc` is reported as faster than `BenchmarkTernaryOperatorIfElse`. Obviously the function could be completely inlined, but that still doesn't account for why it might be _faster_. Does your kernel run agressive power saving (meaning it only cranks up the clock speed when it's been at 100% uptime for a while)? Does changing the order of the tests make any difference? – Martin Kealey Aug 31 '22 at 06:37
1

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

hdias
  • 35
  • 1
  • 5
1

I was playing with a solution that doesn't use the three arguments function. Don't take me wrong, the three arguments solution works great but personally i like to name things explicitly.

What i'd love is an explicit interface like that:

When(<condition>).Then(<true value>).Else(<false value>)

I implemented this like that:

type Else[T any] interface {
    ElseDo(fn func() T) T
    Else(value T) T
}

type Then[T any] interface {
    ThenDo(fn func() T) Else[T]
    Then(value T) Else[T]
}

type Condition[T any] struct {
    condition bool
    thenValue T
    thenFn    func() T
}

func When[T any](condition bool) Then[T] {
    return &Condition[T]{condition: condition}
}

func (c *Condition[T]) ThenDo(fn func() T) Else[T] {
    c.thenFn = fn
    return c
}

func (c *Condition[T]) Then(value T) Else[T] {
    c.thenValue = value
    return c
}

func (c *Condition[T]) ElseDo(fn func() T) T {
    if c.condition {
        return c.then()
    }

    return fn()
}

func (c *Condition[T]) Else(value T) T {
    if c.condition {
        return c.then()
    }

    return value
}

func (c *Condition[T]) then() T {
    if c.thenFn != nil {
        return c.thenFn()
    }
    return c.thenValue
}

Usage:

When[int](something == "expectedValue").Then(0).Else(1)

When[int](value > 0).Then(value).Else(1)

When[int](value > 0).ThenDo(func()int {return value * 4}).Else(1)

When[string](boolean == true).Then("it is true").Else("it is false")

Unfortunately i didn't find a way to get rid of the explicit type when calling the When function. The type is not automatically inferred by the return types of Then/Else ‍♂️

Tommaso Resti
  • 5,800
  • 4
  • 18
  • 34