The null coalesce operator (??)

9,579 views
Skip to first unread message

小菜

unread,
Dec 4, 2015, 1:21:32 AM12/4/15
to golang-dev
The null coalesce operator (??)  is very useful,  It returns its first operand if it exists and is not NULL; otherwise it returns its second operand.

such as PHP:

<?php
// Fetches the value of $_GET['user'] and returns 'nobody'
// if it does not exist.
$username $_GET['user'] ?? 'nobody';
// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

// Coalesces can be chained: this will return the first
// defined value out of $_GET['user'], $_POST['user'], and
// 'nobody'.
$username $_GET['user'] ?? $_POST['user'] ?? 'nobody';
?>

I think this feature is needed in Golang.






Dave Cheney

unread,
Dec 4, 2015, 1:24:16 AM12/4/15
to 小菜, golang-dev

I disagree, nil is not as common in Go as it is in other lanagues because we use error values to indicate an operating failed; returning nil as a sentinel value is strongly discouraged.

Thanks

Dave


--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matthew Dempsky

unread,
Dec 4, 2015, 2:03:36 AM12/4/15
to 小菜, golang-dev
Your example uses strings, but in Go strings aren't nullable.  So at least as presented, it's not directly applicable to Go: it doesn't make sense to have an expression that evaluates to either a string or nil (unless it's of type interface{}).

A variation on the idea would be to recognize "" instead of nil, but then no need to stop there.  You could extend || and && to any comparable type, and "x || y" (respectively "x && y") could evaluate to x without evaluating y if it's non-zero (respectively zero), and otherwise evaluate to y.  That would generalize nicely from the current definitions of || and &&, and the zero-ness of false.

That would let cmd/dist/build.go for example be written like:

    goroot_final = os.Getenv("GOROOT_FINAL") || goroot
    gobin = os.Getenv("GOBIN") || goroot + slash + "bin"
    goos = os.Getenv("GOOS") || gohostos
    goarm = os.Getenv("GOARM") || xgetgoarm()

etc.

Which is kinda cute, but doesn't really strike me as a net improvement.

minux

unread,
Dec 4, 2015, 3:10:23 AM12/4/15
to Matthew Dempsky, 小菜, golang-dev
On Fri, Dec 4, 2015 at 2:03 AM, 'Matthew Dempsky' via golang-dev <golan...@googlegroups.com> wrote:
Your example uses strings, but in Go strings aren't nullable.  So at least as presented, it's not directly applicable to Go: it doesn't make sense to have an expression that evaluates to either a string or nil (unless it's of type interface{}).

A variation on the idea would be to recognize "" instead of nil, but then no need to stop there.  You could extend || and && to any comparable type, and "x || y" (respectively "x && y") could evaluate to x without evaluating y if it's non-zero (respectively zero), and otherwise evaluate to y.  That would generalize nicely from the current definitions of || and &&, and the zero-ness of false.

That would let cmd/dist/build.go for example be written like:

    goroot_final = os.Getenv("GOROOT_FINAL") || goroot
    gobin = os.Getenv("GOBIN") || goroot + slash + "bin"
    goos = os.Getenv("GOOS") || gohostos
    goarm = os.Getenv("GOARM") || xgetgoarm()

This extended short-circuit || is indeed very popular among scripting languages
(It's an idiom in both Javascript and Lua), where (almost) everything is boxed
(or at least everything can be converted to bool implicitly), however, that's not
the case for Go. Moreover, all those languages use dynamically typing, but Go
does not.

Consider this:
   var x interface{}
   y := x || 1

What does it mean? What type should y be? Should y be 1 if x is nil interface?
Or a typed nil?

Note that what the user really want for "y := x || 1" is probably this:
   var y int
   if v, ok := x.(int); ok {
      y = v
   } else {
      y = 1
   }

Matthew Dempsky

unread,
Dec 4, 2015, 4:27:36 AM12/4/15
to minux, 小菜, golang-dev
So supposing this concrete proposal:

  - Generalize the binary logical operators to apply to any comparable value instead of only boolean values.
  - Generalize "p && q" to mean "if p equals its type's zero value then p else q".
  - Generalize "p || q" to mean "if p equals its type's zero value then q else p".

(But to be clear, I'm still not advocating that this is a worthwhile change.)

On Fri, Dec 4, 2015 at 12:09 AM, minux <mi...@golang.org> wrote:
Consider this:
   var x interface{}
   y := x || 1

What does it mean?

It would mean:

    var x interface{}
    var y interface{} = x
    if y == nil {
        y = (interface{})(1)
    }

What type should y be?

interface{}

Should y be 1 if x is nil interface?

Yes.

Or a typed nil?

No.  Typed nils after conversion to interface{} are not equal to interface{}'s zero value.

Note that what the user really want for "y := x || 1" is probably this:
   var y int
   if v, ok := x.(int); ok {
      y = v
   } else {
      y = 1
   }

That would require a different proposal.  Notably, I suspect it would also require *adding* rules to the spec (as opposed to generalizing existing ones like I outlined above), which seems to make it less appealing.

Also, is it your intention that if x's value is (interface{})(double(3.14)), then y's value after "y := x || 1" would still be int(1)?  That would be surprising to me actually.

Note that with the proposal above, you could write:

    y := (x || 1).(int)

which would behave the same as you're suggesting when x is nil or has dynamic type int.  Just it would panic instead of silently replacing non-int values with 1.

Matthew Dempsky

unread,
Dec 4, 2015, 5:18:13 AM12/4/15
to minux, 小菜, golang-dev
On Fri, Dec 4, 2015 at 1:27 AM, Matthew Dempsky <mdem...@google.com> wrote:
So supposing this concrete proposal:

  - Generalize the binary logical operators to apply to any comparable value instead of only boolean values.
  - Generalize "p && q" to mean "if p equals its type's zero value then p else q".
  - Generalize "p || q" to mean "if p equals its type's zero value then q else p".

Actually, even the non-comparable types (functions, maps, and slices) are still comparable against their zero value (the identifier "nil"), so the proposal could further generalize to all types.

Ali Kazai

unread,
Jan 30, 2025, 8:35:26 AMJan 30
to golang-dev
Any update on this proposal ?

Ian Lance Taylor

unread,
Jan 30, 2025, 9:42:21 AMJan 30
to Ali Kazai, golang-dev
On Thu, Jan 30, 2025 at 5:35 AM Ali Kazai <alika...@gmail.com> wrote:
>
> Any update on this proposal ?

As far as I can recall nobody ever opened this as a formal language
change proposal (see https://go.dev/s/proposal).

We now have the cmp.Or function (https://pkg.go.dev/cmp#Or) which has
a similar effect. Given that it's unlikely that we would adopt a
proposal like this one.

Ian
> --
> You received this message because you are subscribed to the Google Groups "golang-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/golang-dev/9260566f-e760-44c7-a468-7900e95d0151n%40googlegroups.com.

Sean Liao

unread,
Jan 30, 2025, 10:31:13 AMJan 30
to golang-dev
I believe it's equivalent to this declined proposal: https://go.dev/issue/42847
proposal: Go 2: Safe navigation operator (?.)

- sean
> To view this discussion visit https://groups.google.com/d/msgid/golang-dev/CAOyqgcWSJCwi_McqWXdSKbw%3DZ3BePVfzvuCeQWKbKJ2CZ1ELXA%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages