Generic "nillable" constraint

2,050 views
Skip to first unread message

Pierre Durand

unread,
Dec 22, 2022, 1:23:22 PM12/22/22
to golang-nuts
Is there a way to declare a "nillable" constraint that matches all types that can be nil (pointer, slice, map, etc.) ?

I would like to write a function, that receives a parameter (with a generic type), and check if this parameter is nil.
I found a very ugly way to write it: https://go.dev/play/p/0g0SoTlBEgs
The problem: the map type needs more than 1 type, so I need to provide the 3 types when I call the `IsNil()` function/
Is there a better way ?

Yes I know I could use `any`, and check with reflect if the value inside the interface is nil.
But I want to know if it's possible to do it with generics.

Thank you.

Pierre Durand

unread,
Dec 22, 2022, 1:24:21 PM12/22/22
to golang-nuts
Same question for `len()`: is there a way to declare a constraint that matches types that support `len()` ?

Axel Wagner

unread,
Dec 22, 2022, 1:39:29 PM12/22/22
to Pierre Durand, golang-nuts
On Thu, Dec 22, 2022 at 2:23 PM Pierre Durand <pierre...@gmail.com> wrote:
Is there a way to declare a "nillable" constraint that matches all types that can be nil (pointer, slice, map, etc.) ?

There is currently no way to do that. In particular, any interface type can be `nil`, therefore any such constraint would have to be satisfied by all interface types.
 
I would like to write a function, that receives a parameter (with a generic type), and check if this parameter is nil.

I don't understand the point, to be honest. It seems to me `== nil` is a better way to spell that, than a function call. And a generic function can always do `v == *new(T)` to check if a value is the zero value.
 
I found a very ugly way to write it: https://go.dev/play/p/0g0SoTlBEgs
The problem: the map type needs more than 1 type, so I need to provide the 3 types when I call the `IsNil()` function/
Is there a better way ?

Yes I know I could use `any`, and check with reflect if the value inside the interface is nil.
But I want to know if it's possible to do it with generics.

Thank you.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/2043afe5-3d18-445a-90a9-75b48d3ec078n%40googlegroups.com.

Axel Wagner

unread,
Dec 22, 2022, 1:43:51 PM12/22/22
to Pierre Durand, golang-nuts
On Thu, Dec 22, 2022 at 2:24 PM Pierre Durand <pierre...@gmail.com> wrote:
Same question for `len()`: is there a way to declare a constraint that matches types that support `len()` ?

For this, the workaround you posted works, at least. It's not convenient, but it also does not seem like a common need - again I'd be kind of curious what you want it for.
 

Le jeudi 22 décembre 2022 à 14:23:22 UTC+1, Pierre Durand a écrit :
Is there a way to declare a "nillable" constraint that matches all types that can be nil (pointer, slice, map, etc.) ?

I would like to write a function, that receives a parameter (with a generic type), and check if this parameter is nil.
I found a very ugly way to write it: https://go.dev/play/p/0g0SoTlBEgs
The problem: the map type needs more than 1 type, so I need to provide the 3 types when I call the `IsNil()` function/
Is there a better way ?

Yes I know I could use `any`, and check with reflect if the value inside the interface is nil.
But I want to know if it's possible to do it with generics.

Thank you.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Pierre Durand

unread,
Dec 22, 2022, 1:51:09 PM12/22/22
to golang-nuts
OK thank you, that confirms what I was thinking.

Indeed it's not a common need.
I wanted to know if it was possible to rewrite https://pkg.go.dev/github.com/stretchr/testify/assert#Nil with a "nillable" constraint instead of "interface{}".


Jon Watte

unread,
Oct 2, 2023, 2:30:40 AM10/2/23
to golang-nuts
>  It seems to me `== nil` is a better way to spell that, than a function call. And a generic function can always do `v == *new(T)` to check if a value is the zero value.

Bringing this up because I found it from a thread on Google.

I want to implement a generic function to "remove nil values from a slice." This should be able to remove nil instances of error, for example.
So, let's say I have:

var arg = []error{nil, errors.New("oh noes"), nil}

Unfortunately, this doesn't work:

func Compact[T comparable](s []T) T {
   ...
   if s[i] == nil {

because "error" is not comparable.
This also doesn't work:

func Compact[T any](s []T) T {
  var zero T
  ...
  if s[i] == zero {

because T is not a comparable constraint.


It may be that "error" is somehow special, and is "the only thing" that doesn't fit neatly into "zero values" or "comparables," but it's a pretty basic fundament of the runtime, so having to special-case everything about it is quite frustrating and cumbersome.


Sincerely

Jon Watte

Patrick Smith

unread,
Oct 2, 2023, 3:35:54 AM10/2/23
to Jon Watte, golang-nuts
On Sun, Oct 1, 2023 at 7:30 PM Jon Watte <jwa...@gmail.com> wrote:
I want to implement a generic function to "remove nil values from a slice." This should be able to remove nil instances of error, for example.
So, let's say I have:

var arg = []error{nil, errors.New("oh noes"), nil}

Unfortunately, this doesn't work:

func Compact[T comparable](s []T) T {
   ...
   if s[i] == nil {

because "error" is not comparable.

Actually, error is comparable. See https://go.dev/play/p/ymBOc5JQyJT

The problem with your code is that "T comparable" guarantees that two values of type T can be compared, but does not guarantee you can compare a value of type T to nil. For example, int is a comparable type, but you can't compare an int to nil. See https://go.dev/play/p/SETrraZ7vkR, which produces the error message: ./prog.go:11:11: invalid operation: t != nil (mismatched types T and untyped nil)

This also doesn't work:

func Compact[T any](s []T) T {
  var zero T
  ...
  if s[i] == zero {

because T is not a comparable constraint.
 
You can fix this by making T comparable instead of any. But then the function will also remove 0 from int slices, "" from string slices, etc. https://go.dev/play/p/NigMasu1aL1

Patrick Smith

unread,
Oct 2, 2023, 3:41:24 AM10/2/23
to Jon Watte, golang-nuts
A quick correction to my own comment:

On Sun, Oct 1, 2023 at 8:35 PM Patrick Smith <pat42...@gmail.com> wrote:
The problem with your code is that "T comparable" guarantees that two values of type T can be compared, 

... but then I remembered the comparison can still panic at runtime, as per https://go.dev/doc/go1.20#language 

Ian Lance Taylor

unread,
Oct 2, 2023, 11:54:24 PM10/2/23
to Patrick Smith, Jon Watte, golang-nuts
There is a lot more on this topic at https://go.dev/issue/61372.
We're not sure how best to handle it.

Ian

Jon Watte

unread,
Oct 3, 2023, 1:51:39 AM10/3/23
to Ian Lance Taylor, Patrick Smith, golang-nuts
What's the concern with "a constant interface that is inhabited exactly by the types that can compare to nil?"

This is clearly something the language has as a concept -- it knows whether "foo == nil" is an error or not.

"nil" could be the ergonomic name for this constraint:

func Compact[T nil](s []T) ...

Sincerely,

Jon

Axel Wagner

unread,
Oct 3, 2023, 4:26:50 AM10/3/23
to Jon Watte, Ian Lance Taylor, Patrick Smith, golang-nuts
It doesn't solve the problem. That function signature you wrote could not be instantiated by `int`, for example. You can't write `comparable | nil`, as `comparable` is not allowed in a union. And if we allowed it, there would be no way to write the body of the function. It doesn't help with types like `struct{ F func() }`. And it doesn't make it easier to write the zero value for returns etc. And it doesn't address the same kind of issue for generated code.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Jon Watte

unread,
Oct 3, 2023, 7:07:09 PM10/3/23
to Axel Wagner, Ian Lance Taylor, Patrick Smith, golang-nuts
I don't want to include int or struct{} in this case. I care specifically about "can be compared to nil"
The only thing I can do with a type parameter that is only constrained as "nil" is compare it to nil, or assign nil to it.
This means approximately "any reference type" -- interfaces, pointers, maps, slices, chans...

But, let's put this out there:
How would you, today, write a function that compacted a slice of interface, such that any "nil" interface value would be removed from the slice?

func Compact[T ?](sl []T) []T {
    j := 0
    for i := 0; i < len(sl); i++ {
        if el := sl[i]; el != nil {
            sl[j] = el
            j++
        }
    }
    return sl[:j]
}

There's nothing today I can put in place of the "?" to make this perfectly-reasonable generic function work.
I propose I should be able to put "nil" in place of the "?" to make this work, and it would work for any reference type (that can be compared or assigned to nil.)

Unless I'm missing something?

Sincerely,

Jon Watte


--
"I find that the harder I work, the more luck I seem to have." -- Thomas Jefferson

Ian Lance Taylor

unread,
Oct 3, 2023, 7:33:22 PM10/3/23
to Jon Watte, Axel Wagner, Patrick Smith, golang-nuts
On Tue, Oct 3, 2023 at 12:06 PM Jon Watte <jwa...@gmail.com> wrote:
>
> I don't want to include int or struct{} in this case. I care specifically about "can be compared to nil"
> The only thing I can do with a type parameter that is only constrained as "nil" is compare it to nil, or assign nil to it.
> This means approximately "any reference type" -- interfaces, pointers, maps, slices, chans...

It's not obvious that that is an interesting set of types. How often
do people want to write a generic function that permits any type that
can be compared to nil but does not permit any numeric or string type?


> But, let's put this out there:
> How would you, today, write a function that compacted a slice of interface, such that any "nil" interface value would be removed from the slice?

Unless I misunderstand, you seem to have shifted the goal a bit.
There is currently no way to restrict a type argument to be an
interface type, with or without your suggestion of a nil constraint.


> func Compact[T ?](sl []T) []T {
> j := 0
> for i := 0; i < len(sl); i++ {
> if el := sl[i]; el != nil {
> sl[j] = el
> j++
> }
> }
> return sl[:j]
> }
>
> There's nothing today I can put in place of the "?" to make this perfectly-reasonable generic function work.
> I propose I should be able to put "nil" in place of the "?" to make this work, and it would work for any reference type (that can be compared or assigned to nil.)
>
> Unless I'm missing something?

Well, here is a version that works for a slice of any type, including
an interface type. Arguably, though, using reflect is cheating.

func Compact[T any](s []T) []T {
j := 0
for _, v := range s {
vv := reflect.ValueOf(v)
if vv.IsValid() && !vv.IsZero() {
s[j] = v
j++
}
}
return s[:j]
}

Ian

Axel Wagner

unread,
Oct 3, 2023, 7:33:45 PM10/3/23
to golang-nuts
On Tue, Oct 3, 2023 at 9:06 PM Jon Watte <jwa...@gmail.com> wrote:
I don't want to include int or struct{} in this case. I care specifically about "can be compared to nil"
The only thing I can do with a type parameter that is only constrained as "nil" is compare it to nil, or assign nil to it.
This means approximately "any reference type" -- interfaces, pointers, maps, slices, chans...

Exactly. That is the problem. It means your suggestion does not allow a slew of problems that #61372 would solve. It means your suggestion is less powerful.
 
But, let's put this out there:
How would you, today, write a function that compacted a slice of interface, such that any "nil" interface value would be removed from the slice?

func Compact[T ?](sl []T) []T {
    j := 0
    for i := 0; i < len(sl); i++ {
        if el := sl[i]; el != nil {
            sl[j] = el
            j++
        }
    }
    return sl[:j]
}

There's nothing today I can put in place of the "?" to make this perfectly-reasonable generic function work.
I propose I should be able to put "nil" in place of the "?" to make this work, and it would work for any reference type (that can be compared or assigned to nil.)

With #61372, you would put `any` and write `el != zero`. And it would work for all types, including the ones you mention.

Jon Watte

unread,
Oct 3, 2023, 11:33:43 PM10/3/23
to Ian Lance Taylor, Axel Wagner, Patrick Smith, golang-nuts

It's not obvious that that is an interesting set of types.  How often
> do people want to write a generic function that permits any type that
> can be compared to nil but does not permit any numeric or string type?

Well I've run into this with some frequency, and a bunch of other developers here have similarly moaned that this is a big gaping hole in the constraint system.
So, at least from our corner of the world, this is a legitimate desire.
There's a few other questions like this on the internet, too, so I don't think "we" are particularly special.

> Unless I misunderstand, you seem to have shifted the goal a bit.
> There is currently no way to restrict a type argument to be an
> interface type, with or without your suggestion of a nil constraint.

I have not shifted the goal. This very function has been my particular goal in this thread from the start, because it clearly illustrates the need for this constraint.
The only reason any other possible solution (like "zero values") was suggested in this thread, was as a possible work-around for the lack of a `nil` constraint.
(Note that this particular function could usefully be applied to slices of maps, slices of chans, and so on -- none of which are comparable.)

"assign to nil" is also pretty useful as a generic constraint, because that also includes "return a nil literal."
This can combo nicely with some other interfaces, such as JSON or YAML or protobuf marshaling/demarshaling.

So, for illustration:

func LogJSON[T nil](name string, arg T) {
    if arg == nil {
        Log(name, "is empty")
    } else {
        data := Infallible(json.Marshal(arg))
        Log(name, string(data))
    }
}

(The actual, production uses of these kinds of affordances are typically a little more involved, but we would save a lot of boilerplate/repeated code if this constraint were available.)
(Also, Infallible() could be a good standard library function, because it's common enough, but that's a totally different discussion that's not related here.)

Sincerely,

Jon Watte
--
"I find that the harder I work, the more luck I seem to have." -- Thomas Jefferson

Jon Watte

unread,
Oct 4, 2023, 12:57:34 AM10/4/23
to Ian Lance Taylor, Axel Wagner, Patrick Smith, golang-nuts
> Can you give us an example?

I already gave examples! They're in the thread! In reality, we have the insides of a compiler where this would be helpful in more complex cases, and we have the insides of a graphql and JSON web interface where this would be helpful in a more complex case, but I'm not about to post the hundreds of lines of code that would be needed to set those up.

For another example, I have a slice of chans, some of which are nil. I'd like to compact that to only the non-nil chans.
I can make a chan-specific slice compactor that's generic on the chan type. But why shouldn't I be able to also use that for errors, or maps, or pointers?

Why do I need:
func CompactChan[T any](ch []chan T) ...
func CompactMap[K comparable, V any](m []map[K, V]) ...
func CompactPointer[T any; U *T](sl []U) ...
...
and this still can't be used on `[]error` (other than with explicit types, or using the more-powerful comparable constraint)

The main problem here is that the language has a set of "comparable values" and a set of "comparable to nil" values and they only partially overlap.
Because the language has a type semantic of "can be compared or assigned to nil," then that clearly should be expressible in the generic type system, unless you for some reason want the the generic type system to be deliberately less expressive than the full type system, and deliberately want to exclude use cases like these.

Sincerely,

Jon Watte


--
"I find that the harder I work, the more luck I seem to have." -- Thomas Jefferson


On Tue, Oct 3, 2023 at 5:32 PM Ian Lance Taylor <ia...@golang.org> wrote:
On Tue, Oct 3, 2023 at 4:33 PM Jon Watte <jwa...@gmail.com> wrote:
>
>
>
> > It's not obvious that that is an interesting set of types.  How often
> > do people want to write a generic function that permits any type that
> > can be compared to nil but does not permit any numeric or string type?
>
> Well I've run into this with some frequency, and a bunch of other developers here have similarly moaned that this is a big gaping hole in the constraint system.
> So, at least from our corner of the world, this is a legitimate desire.
> There's a few other questions like this on the internet, too, so I don't think "we" are particularly special.

Can you give us an example?  There are clear reasons for being able to
compare a type to the zero value of that type.  But it's not clear why
it's useful to focus on the set of types for which the zero value is
written as "nil".  For example, why wouldn't I want to call your
LogJSON example with a string?  Thanks.

Ian

Ian Lance Taylor

unread,
Oct 4, 2023, 1:03:05 AM10/4/23
to Jon Watte, Axel Wagner, Patrick Smith, golang-nuts
On Tue, Oct 3, 2023 at 5:57 PM Jon Watte <jwa...@gmail.com> wrote:
>
> > Can you give us an example?
>
> I already gave examples! They're in the thread! In reality, we have the insides of a compiler where this would be helpful in more complex cases, and we have the insides of a graphql and JSON web interface where this would be helpful in a more complex case, but I'm not about to post the hundreds of lines of code that would be needed to set those up.

Thanks, but I'm looking for examples where it is important to permit
only type arguments that can be compared to nil. Your examples seem
to me to be cases that would be addressed if we had a standard way to
compare a type to its zero value, such as https://go.dev/issue/61372
or https://go.dev/issue/62487 I'm asking whether there are cases
where we want the former but not the latter. Thanks.

Ian

Jon Watte

unread,
Oct 4, 2023, 4:55:03 AM10/4/23
to Ian Lance Taylor, Axel Wagner, Patrick Smith, golang-nuts
> where it is important to permit only type arguments that can be compared to nil


I see! As in, if we somehow got a "equalszero" constraint, then that constraint would solve the problem I illustrate.
I believe that assertion is correct, but I also believe that is a stronger assertion, and also that it introduces more of a new concept than a simple "nil" constraint. (Unless you're looking for some way to make "any" work, and introduce a zero keyword or something...)

Also, there's the ergonomics of having to make a zero value instance. Maybe we can rely on the compiler to optimize it away, but at a minimum it adds another required line of code in the implementation. E g:

func MaybeNuke[T nil](b bool, val T) T {
  if b {
    return nil
  }
  return val
}

func MaybeNuke(T zero](b bool, val T) T {
  if b {
    var nope T // an extra line!
    return nope
  }
  return val
}

func MaybeNuke(T any](b bool, val T) T {
  if b {
    return zero[T]{} // maybe? seems weird
  }
  return val
}

This is because not all zero values can be instantiated inline with simply T{}.

Axel Wagner

unread,
Oct 4, 2023, 5:34:28 AM10/4/23
to Jon Watte, Ian Lance Taylor, Patrick Smith, golang-nuts
On Wed, Oct 4, 2023 at 6:54 AM Jon Watte <jwa...@gmail.com> wrote:
> where it is important to permit only type arguments that can be compared to nil

I see! As in, if we somehow got a "equalszero" constraint, then that constraint would solve the problem I illustrate.
I believe that assertion is correct, but I also believe that is a stronger assertion, and also that it introduces more of a new concept than a simple "nil" constraint. (Unless you're looking for some way to make "any" work, and introduce a zero keyword or something...)

Yes, that is what #61372 proposes: Introduce a `zero` predeclared identifier (!) that is assignable to any type and comparable to any type. With some discussion about whether it should only apply inside generic code or not. There is no proposal (as far as I know) for anything like an "equalszero" constraint, as every type can be assigned a meaningful comparison to its zero value, so it seems we should just allow it for all types.

To be clear, the criticism of a `nilable` constraint is
1. It only solves a subset of the problem we are seeing. You gave examples from that subset. I gave some examples of problems we are seeing that are *not* in that subset.
2. It is not really clear this particular subset is particularly important. Why is the specific split into (interfaces, pointers, slices, functions, maps, channels) and (numbers, booleans, strings, structs, arrays) a particularly important one?
3. As long as that is not clear, it seems more prudent to focus on mechanisms that solve more of the problems we are seeing.

FWIW I could, personally, get more (though still not fully) on board with an `isinterface` constraint, that would allow only interfaces. It would still allow assignment and comparison to `nil`. But it seems far clearer to me, that interfaces can be singled out. While a `nil` interface is categorically an invalid value, the same is not true for `nil` pointers/maps/channels/funcs in general. Any of those kinds of types could still have methods callable on them that work perfectly fine (by doing an `if receiver == nil` check in the method). You categorically can't call a method on a `nil` interface.

And an `isinterface` constraint could still conceivable be useful for many of the examples you mentioned. Or it would allow

func Convert[J isinterface, T I](s []T) []J {
    out := make([]I, len(T))
    for i, v := range s {
        out[i] = J(v)
    }
    return out
}

I'd still not be convinced this is really worth it, but at least it seems clearer why that particular subset of types deserves to be singled out. In fact, many people have argued that the interface zero value really shouldn't have been spelled `nil`, because interfaces have so little in common, conceptually, to other "nilable" types.

Axel Wagner

unread,
Oct 4, 2023, 5:36:53 AM10/4/23
to Jon Watte, Ian Lance Taylor, Patrick Smith, golang-nuts
(correction: It should be Convert[J isinterface, T J]. I changed the name from I to J to be more readable and then missed one occurrence)

Axel Wagner

unread,
Oct 4, 2023, 5:41:35 AM10/4/23
to Jon Watte, Ian Lance Taylor, Patrick Smith, golang-nuts
Oh (sorry, being forgetful) and re "it's less of a new mechanism than introducing a zero identifier": #62487 introduces even less new mechanism, by expanding comparison to (and assignment of) `nil` to all types inside a generic function. It's not a new class of constraint, it just special-cases `nil` a bit more. So it is still a far more general mechanism, that solves more problems than `nilable` constraint, while requiring fewer (or at worst the same number of) new concepts.

Jon Watte

unread,
Oct 18, 2023, 4:10:20 AM10/18/23
to Axel Wagner, Ian Lance Taylor, Patrick Smith, golang-nuts
Circling back to this, because it came up today again.

Here's the generic function I want to write. It comes up in a lot of function composition, which in turn comes up in a lot of interface adapters and such:

func maybeAssign[T any](dst *T, src T, name string) {
    if *dst != nil { // any can't be compared with nil
        panic(fmt.Errorf("too many %s arguments", name))
    }
    *dst = src
}

Using this function in each assignment, instead of inlining the four-line construct with panic, can save a lot of space and make code a lot more readable.
The above doesn't work, because not every type can be assigned nil.
The following also doesn't work:

func maybeAssign[T comparable](dst *T, src T, name string) {
    var zero T
    if *dst != zero { // interface and other nillable types can't be compared to zero
        panic(fmt.Errorf("too many %s arguments", name))
    }
    *dst = src
}

Because interface values aren't comparable. (As aren't chans, maps, etc, but THOSE can be jammed into various interface constructs, whereas "any interface" cannot, because "interface{}" doesn't actually mean "any interface")

Let me try to answer:

> Why is the specific split into (interfaces, pointers, slices, functions, maps, channels) and (numbers, booleans, strings, structs, arrays) a particularly important one?

Because, while go tries very hard to make sure every storable type has a "zero value," it somehow decides that you can't necessarily COMPARE to that zero value.
But the whole point of zero values is that you can tell them from non-zero values!
So, the language has introduced a work-around with the concept of "I can compare to nil" for these reference types that aren't comparable to their zero value.
But generics don't allow us to sense or make use of this, so generics can't express what the regular language can express. Even a very simple case like the above, can't currently be expressed, and this leads to more verbose code that's harder to read and harder to work with. (Granted, this is my opinion, but I'm not alone.)

If the language instead changes such that chans and interfaces and maps become comparable, then that's fine -- that solves the problem! But that seems like a much bigger change than a constraint that senses exactly the "can compare to nil" semantic.

If the language instead changes so that nil means "the zero value" in general, and it so happens that these nil-comparable types can be compared to nil without any particular qualification, that also solves the problem. That might indeed be a good solution -- but if so, it'd be nice to know what we can do to make that happen, and what the other opposition to that change might be, because that change also feels much less narrow than a "nil" type constraint.


Sincerely,

Jon Watte


--
"I find that the harder I work, the more luck I seem to have." -- Thomas Jefferson

Bakul Shah

unread,
Oct 18, 2023, 4:27:03 AM10/18/23
to Jon Watte, Axel Wagner, Ian Lance Taylor, Patrick Smith, golang-nuts
I'd happy with

if val { ... }

and

if !val { ... }

Instead of comparing val with an explicit zero but don't feel strongly about this.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Axel Wagner

unread,
Oct 18, 2023, 5:26:12 AM10/18/23
to Jon Watte, Ian Lance Taylor, Patrick Smith, golang-nuts
On Wed, Oct 18, 2023 at 6:09 AM Jon Watte <jwa...@gmail.com> wrote:
Circling back to this, because it came up today again.

Here's the generic function I want to write. It comes up in a lot of function composition, which in turn comes up in a lot of interface adapters and such:

func maybeAssign[T any](dst *T, src T, name string) {
    if *dst != nil { // any can't be compared with nil
        panic(fmt.Errorf("too many %s arguments", name))
    }
    *dst = src
}

Using this function in each assignment, instead of inlining the four-line construct with panic, can save a lot of space and make code a lot more readable.
The above doesn't work, because not every type can be assigned nil.
The following also doesn't work:

func maybeAssign[T comparable](dst *T, src T, name string) {
    var zero T
    if *dst != zero { // interface and other nillable types can't be compared to zero
        panic(fmt.Errorf("too many %s arguments", name))
    }
    *dst = src
}

Because interface values aren't comparable. (As aren't chans, maps, etc, but THOSE can be jammed into various interface constructs, whereas "any interface" cannot, because "interface{}" doesn't actually mean "any interface")

Let me try to answer:

> Why is the specific split into (interfaces, pointers, slices, functions, maps, channels) and (numbers, booleans, strings, structs, arrays) a particularly important one?

Because, while go tries very hard to make sure every storable type has a "zero value," it somehow decides that you can't necessarily COMPARE to that zero value.
But the whole point of zero values is that you can tell them from non-zero values!
So, the language has introduced a work-around with the concept of "I can compare to nil" for these reference types that aren't comparable to their zero value.
But generics don't allow us to sense or make use of this, so generics can't express what the regular language can express. Even a very simple case like the above, can't currently be expressed, and this leads to more verbose code that's harder to read and harder to work with. (Granted, this is my opinion, but I'm not alone.)

That does not actually answer the question, though. Again, note that your problem would be solved both by #61372 (you could write `if *dst != zero`) and by #62487 (you could just write `if *dst != nil`), neither of which require you to make a distinction between "nilable" types and "non-nilable" types. In fact, it would make your `maybeAssign` function worse - it would be less general, because it could only be used with a subset of types and it's not clear why that subset is a good one.

(also, nit: channels are comparable)

If the language instead changes so that nil means "the zero value" in general, and it so happens that these nil-comparable types can be compared to nil without any particular qualification, that also solves the problem.

Right. That is what my question was getting at.
 
That might indeed be a good solution -- but if so, it'd be nice to know what we can do to make that happen, and what the other opposition to that change might be, because that change also feels much less narrow than a "nil" type constraint.

Being "less narrow" can mean two things: It can mean "it is a more general solution" and it can mean "it is a bigger change". The two proposals above are a similarly big change, that are more general in the kinds of problems they solve. So they seem better.

Jon Watte

unread,
Oct 18, 2023, 3:25:22 PM10/18/23
to Axel Wagner, Ian Lance Taylor, Patrick Smith, golang-nuts
> The two proposals above are a similarly big change, that are more general in the kinds of problems they solve. So they seem better.

Well, if that would happen, my complaint would go away, so I'd be happy with that.

I think "making all values comparable" is a worse change though (there's a reason they aren't comparable!) and making everything comparable to nil without type qualification is better IMO.


Sincerely,

Jon Watte


--
"I find that the harder I work, the more luck I seem to have." -- Thomas Jefferson

Axel Wagner

unread,
Oct 19, 2023, 5:19:59 AM10/19/23
to golang-nuts
On Wed, Oct 18, 2023 at 5:24 PM Jon Watte <jwa...@gmail.com> wrote:
I think "making all values comparable" is a worse change though (there's a reason they aren't comparable!)

FTR the proposal was not to make all values comparable, but to make all values comparable to the predeclared identifier nil - similar to how, currently, a lot of non-comparable values are comparable to the predeclared identifier nil.
Reply all
Reply to author
Forward
0 new messages