Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

[ generics] Moving forward with the generics design draft

14,220 views
Skip to first unread message

Ian Lance Taylor

unread,
Aug 20, 2020, 8:28:23 PM8/20/20
to golang-nuts, Robert Griesemer
After many discussions and reading many comments, we plan to move
forward with some changes and clarifications to the generics design
draft.

1.

We’re going to settle on square brackets for the generics syntax.
We’re going to drop the “type” keyword before type parameters, as
using square brackets is sufficient to distinguish the type parameter
list from the ordinary parameter list. To avoid the ambiguity with
array declarations, we will require that all type parameters provide a
constraint. This has the advantage of giving type parameter lists the
exact same syntax as ordinary parameter lists (other than using square
brackets). To simplify the common case of a type parameter that has
no constraints, we will introduce a new predeclared identifier “any”
as an alias for “interface{}”.

The result is declarations that look like this:

type Vector[T any] []T
func Print[T any](s []T) { … }
func Index[T comparable](s []T, e T) { … }

We feel that the cost of the new predeclared identifier “any” is
outweighed by the simplification achieved by making all parameter
lists syntactically the same: as each regular parameter always has a
type, each type parameter always has a constraint (its meta-type).

Changing “[type T]” to “[T any]” seems about equally readable and
saves one character. We’ll be able to streamline a lot of existing
code in the standard library and elsewhere by replacing “interface{}”
with “any”.

2.

We’re going to simplify the rule for type list satisfaction. The type
argument will satisfy the constraint if the type argument is identical
to any type in the type list, or if the underlying type of the type
argument is identical to any type in the type list. What we are
removing here is any use of the underlying types of the types in the
type list. This tweaked rule means that the type list can decide
whether to accept an exact defined type, other than a predeclared
type, or whether to accept any type with a matching underlying type.

This is a subtle change that we don’t expect to affect any existing
experimental code.

We think that this definition might work if we permit interface types
with type lists to be used outside of type constraints. Such
interfaces would effectively act like sum types. That is not part of
this design draft, but it’s an obvious thing to consider for the
future.

Note that a type list can mention type parameters (that is, other type
parameters in the same type parameter list). These will be checked by
first replacing the type parameter(s) with the corresponding type
argument(s), and then using the rule described above.

3.

We’re going to clarify that when considering the operations permitted
for a value whose type is a type parameter, we will ignore the methods
of any types in the type list. The general rule is that the generic
function can use any operation permitted by every type in the type
list. However, this will only apply to operators and predeclared
functions (such as "len" and "cap"). It won’t apply to methods, for
the case where the type list includes a list of types that all define
some method. Any methods must be listed separately in the interface
type, not inherited from the type list.

This rule seems generally clear, and avoids some complex reasoning
involving type lists that include structs with embedded type
parameters.

4.

We’re going to permit type switches on type parameters that have type
lists, without the “.(type)” syntax. The “(.type)” syntax exists to
clarify code like “switch v := x.(type)”. A type switch on a type
parameter won’t be able to use the “:=” syntax anyhow, so there is no
reason to require “.(type)”. In a type switch on a type parameter
with a type list, every case listed must be a type that appears in the
type list (“default” is also permitted, of course). A case will be
chosen if it is the type matched by the type argument, although as
discussed above it may not be the exact type argument: it may be the
underlying type of the type argument. To make that rule very clear,
type switches will not be permitted for type parameters that do not
have type lists. It is already possible to switch on a value “x”
whose type is a type parameter without a type list by writing code
like “switch (interface{})(x).(type)” (which may now be written as
“switch any(x).(type)”). That construct is not the simplest, but it
uses only features already present in the language, and we don’t
expect it to be widely needed.


These changes will soon be implemented in the experimental design on
the dev.generics branch, and in the go2go playground. Some of them
already work. We will update the design draft accordingly.


We welcome any comments. Thanks for all the help that so many people
have provided so far.

Ian & Robert

David Riley

unread,
Aug 20, 2020, 9:51:41 PM8/20/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
On Aug 20, 2020, at 20:27, Ian Lance Taylor <ia...@golang.org> wrote:
>
> 1.
>
> We’re going to settle on square brackets for the generics syntax.
> We’re going to drop the “type” keyword before type parameters, as
> using square brackets is sufficient to distinguish the type parameter
> list from the ordinary parameter list. To avoid the ambiguity with
> array declarations, we will require that all type parameters provide a
> constraint. This has the advantage of giving type parameter lists the
> exact same syntax as ordinary parameter lists (other than using square
> brackets). To simplify the common case of a type parameter that has
> no constraints, we will introduce a new predeclared identifier “any”
> as an alias for “interface{}”.
>
> The result is declarations that look like this:
>
> type Vector[T any] []T
> func Print[T any](s []T) { … }
> func Index[T comparable](s []T, e T) { … }
>
> We feel that the cost of the new predeclared identifier “any” is
> outweighed by the simplification achieved by making all parameter
> lists syntactically the same: as each regular parameter always has a
> type, each type parameter always has a constraint (its meta-type).
>
> Changing “[type T]” to “[T any]” seems about equally readable and
> saves one character. We’ll be able to streamline a lot of existing
> code in the standard library and elsewhere by replacing “interface{}”
> with “any”.

Resounding “yes” from me. I like this a lot. Good choice.

The rest also sounds good (I think; I may be misunderstanding some of the bits about type switches but I think it sounds good), but #1 in particular should hopefully end the bikeshedding because it seems a good all-around compromise to me.


- DavE

Jan Mercl

unread,
Aug 20, 2020, 10:22:42 PM8/20/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
On Fri, Aug 21, 2020 at 2:28 AM Ian Lance Taylor <ia...@golang.org> wrote:

> To simplify the common case of a type parameter that has
> no constraints, we will introduce a new predeclared identifier “any”
> as an alias for “interface{}”.

Anyone can write the declaration

type any = interface{}

today and possibly some people already do that. But in my opinion that
would, so far, not pass a code review within the Go project per se.
(It would definitely not pass my code review.)

I don't like it and It makes me sad it is being proposed to become
officially blessed.

Robert Engels

unread,
Aug 20, 2020, 11:20:25 PM8/20/20
to Jan Mercl, Ian Lance Taylor, golang-nuts, Robert Griesemer
I like it. Well done.

> On Aug 20, 2020, at 9:22 PM, Jan Mercl <0xj...@gmail.com> wrote:
> --
> 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/CAA40n-XDYjhoMXt%3DEP82EPSrrbmM2D4OsgtEwdx38h7HU5DwWw%40mail.gmail.com.

jimmy frasche

unread,
Aug 20, 2020, 11:28:57 PM8/20/20
to Robert Engels, Jan Mercl, Ian Lance Taylor, golang-nuts, Robert Griesemer
To clarify on the type switches, would it have to be used like this:

type C interface {
type X, Y
}

func f(x X) X {
return x
}

func g[T C](v T) T {
switch v {
case X:
// to use v as an X
// we must convert
x0 := X(v)
x1 := f(x0)
// to use x1 as a T
// we must convert back
t := T(x1)
return t
case Y:
return v
}
}

And that the lack of a dedicated syntax to distinguish the case like
.[type] also means that you could no longer write

func h[T comparable](x, a, b T) {
switch x {
case a:
case b:
}
}

Regardless, all of these changes are fantastic!
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/34314A4C-79B4-4913-9404-26EF1267115F%40ix.netcom.com.

burak serdar

unread,
Aug 20, 2020, 11:53:40 PM8/20/20
to Jan Mercl, Ian Lance Taylor, golang-nuts, Robert Griesemer
Is "any" truly an alias for interface{}, or is it only a constraint? I
agree with Jan's point that replacing interface{} with "any" might
unintentionally encourage its use.

Ian Lance Taylor

unread,
Aug 21, 2020, 12:54:47 AM8/21/20
to burak serdar, Jan Mercl, golang-nuts, Robert Griesemer
On Thu, Aug 20, 2020 at 8:52 PM burak serdar <bse...@computer.org> wrote:
>
> On Thu, Aug 20, 2020 at 8:22 PM Jan Mercl <0xj...@gmail.com> wrote:
> >
> > On Fri, Aug 21, 2020 at 2:28 AM Ian Lance Taylor <ia...@golang.org> wrote:
> >
> > > To simplify the common case of a type parameter that has
> > > no constraints, we will introduce a new predeclared identifier “any”
> > > as an alias for “interface{}”.
> >
> > Anyone can write the declaration
> >
> > type any = interface{}
> >
> > today and possibly some people already do that. But in my opinion that
> > would, so far, not pass a code review within the Go project per se.
> > (It would definitely not pass my code review.)
> >
> > I don't like it and It makes me sad it is being proposed to become
> > officially blessed.
>
> Is "any" truly an alias for interface{}, or is it only a constraint? I
> agree with Jan's point that replacing interface{} with "any" might
> unintentionally encourage its use.

Our intent here is that "any" will be available for all code. Yes, we
wouldn't do it if it weren't for its use as a type constraint. But if
we are going to do it for type constraints, there seems to be little
benefit to restricting it to only work as a type constraint.

This is not, of course, a new idea, even in the absence of generics.
For example, https://golang.org/issue/33232. (My comment there was
that we would use interface{} less if we had generics, but of course
when we require type constraints then we actually wind up using it
somewhat more.)

Ian

Ian Lance Taylor

unread,
Aug 21, 2020, 12:56:57 AM8/21/20
to jimmy frasche, Robert Engels, Jan Mercl, golang-nuts, Robert Griesemer
No, the intent is that you would switch on the type parameter itself,
not a value.

func g[T C](v T) T {
switch T {
// the rest is the same
}
}

Thanks for asking.

Ian

burak serdar

unread,
Aug 21, 2020, 1:40:21 AM8/21/20
to Ian Lance Taylor, Jan Mercl, golang-nuts, Robert Griesemer
On Thu, Aug 20, 2020 at 10:54 PM Ian Lance Taylor <ia...@golang.org> wrote:
>
> On Thu, Aug 20, 2020 at 8:52 PM burak serdar <bse...@computer.org> wrote:
> >
> > On Thu, Aug 20, 2020 at 8:22 PM Jan Mercl <0xj...@gmail.com> wrote:
> > >
> > > On Fri, Aug 21, 2020 at 2:28 AM Ian Lance Taylor <ia...@golang.org> wrote:
> > >
> > > > To simplify the common case of a type parameter that has
> > > > no constraints, we will introduce a new predeclared identifier “any”
> > > > as an alias for “interface{}”.
> > >
> > > Anyone can write the declaration
> > >
> > > type any = interface{}
> > >
> > > today and possibly some people already do that. But in my opinion that
> > > would, so far, not pass a code review within the Go project per se.
> > > (It would definitely not pass my code review.)
> > >
> > > I don't like it and It makes me sad it is being proposed to become
> > > officially blessed.
> >
> > Is "any" truly an alias for interface{}, or is it only a constraint? I
> > agree with Jan's point that replacing interface{} with "any" might
> > unintentionally encourage its use.
>
> Our intent here is that "any" will be available for all code. Yes, we
> wouldn't do it if it weren't for its use as a type constraint. But if
> we are going to do it for type constraints, there seems to be little
> benefit to restricting it to only work as a type constraint.

What worries me is code like this:

func f() any {
int *i
return i
}

func main() {
if f()==nil {
...
}
}

Use of "any" makes it look like f returns an *int and f() is nil, but
it is not, because "any" is interface{}.

I think "any" as a constraint is useful, like "comparable", but "any"
as a type is misleading.

Kurtis Rader

unread,
Aug 21, 2020, 1:54:41 AM8/21/20
to burak serdar, Ian Lance Taylor, Jan Mercl, golang-nuts, Robert Griesemer
On Thu, Aug 20, 2020 at 10:40 PM burak serdar <bse...@computer.org> wrote:
What worries me is code like this:

func f() any {
   int *i
  return i
}

func main() {
   if f()==nil {
    ...
   }
}

Use of "any" makes it look like f returns an *int and f() is nil, but
it is not, because "any" is interface{}.

I think "any" as a constraint is useful, like "comparable", but "any"
as a type is misleading.

Isn't your example just a case of confusing a nil interface with a nil value inside a generic interface? How does requiring writing it as `func f() interface{} {` make the behavior any clearer?

--
Kurtis Rader
Caretaker of the exceptional canines Junior and Hank

Kurtis Rader

unread,
Aug 21, 2020, 2:19:34 AM8/21/20
to burak serdar, Ian Lance Taylor, Jan Mercl, golang-nuts, Robert Griesemer
On Thu, Aug 20, 2020 at 10:53 PM Kurtis Rader <kra...@skepticism.us> wrote:
Isn't your example just a case of confusing a nil interface with a nil value inside a generic interface? How does requiring writing it as `func f() interface{} {` make the behavior any clearer?

And the, possibly canonical, discussion about this: https://groups.google.com/g/golang-nuts/c/wnH302gBa4I/discussion

Bakul Shah

unread,
Aug 21, 2020, 2:22:51 AM8/21/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
On Aug 20, 2020, at 5:27 PM, Ian Lance Taylor <ia...@golang.org> wrote:
>
> After many discussions and reading many comments, we plan to move
> forward with some changes and clarifications to the generics design
> draft.
>
> 1.
>
> We’re going to settle on square brackets for the generics syntax.
> We’re going to drop the “type” keyword before type parameters, as
> using square brackets is sufficient to distinguish the type parameter
> list from the ordinary parameter list. To avoid the ambiguity with
> array declarations, we will require that all type parameters provide a
> constraint. This has the advantage of giving type parameter lists the
> exact same syntax as ordinary parameter lists (other than using square
> brackets). To simplify the common case of a type parameter that has
> no constraints, we will introduce a new predeclared identifier “any”
> as an alias for “interface{}”.

Great!


> 2.
>
> We’re going to simplify the rule for type list satisfaction. The type
> argument will satisfy the constraint if the type argument is identical
> to any type in the type list, or if the underlying type of the type
> argument is identical to any type in the type list. What we are
> removing here is any use of the underlying types of the types in the
> type list. This tweaked rule means that the type list can decide
> whether to accept an exact defined type, other than a predeclared
> type, or whether to accept any type with a matching underlying type.
>
> This is a subtle change that we don’t expect to affect any existing
> experimental code.
>
> We think that this definition might work if we permit interface types
> with type lists to be used outside of type constraints. Such
> interfaces would effectively act like sum types. That is not part of
> this design draft, but it’s an obvious thing to consider for the
> future.
>
> Note that a type list can mention type parameters (that is, other type
> parameters in the same type parameter list). These will be checked by
> first replacing the type parameter(s) with the corresponding type
> argument(s), and then using the rule described above.

Still uncomfortable with this. Will try to expand on this in a separate
email.

> 3.
>
> We’re going to clarify that when considering the operations permitted
> for a value whose type is a type parameter, we will ignore the methods
> of any types in the type list. The general rule is that the generic
> function can use any operation permitted by every type in the type
> list. However, this will only apply to operators and predeclared
> functions (such as "len" and "cap"). It won’t apply to methods, for
> the case where the type list includes a list of types that all define
> some method. Any methods must be listed separately in the interface
> type, not inherited from the type list.
>
> This rule seems generally clear, and avoids some complex reasoning
> involving type lists that include structs with embedded type
> parameters.

You seem to be saying a generic function can use operator X only if
if *every* type in the type list implements it. Thus if I have

type foo interface { int; someSLice }

I can't use + and I can't use len(), right?

Axel Wagner

unread,
Aug 21, 2020, 3:38:06 AM8/21/20
to golang-nuts
Just to clarify, the intent is to make the declaration in the spec `type any = interface{}`, not `type any interface{}`, correct? The latter would be more analogous to `error`. Either has certain advantages and disadvantages, I'm not sure which I prefer, but I just want to make sure I understand the plan :)

> But in my opinion that would, so far, not pass a code review within the Go project per se. (It would definitely not pass my code review.)

It wouldn't pass my review either - but the only reason for that is that it trades off the overhead of looking up the definition of `any` for the convenience of typing/reading `interface{}`. With a predeclared identifier that's part of the language, the downsides of this tradeoff vanish.

> Use of "any" makes it look like f returns an *int and f() is nil, but it is not, because "any" is interface{}.

Apart from what others have said in general, the elephant in the room is, of course, `error`. I don't think pre-declaring a name for an interface changes the equation here.

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

David Riley

unread,
Aug 21, 2020, 8:36:08 AM8/21/20
to Ian Lance Taylor, jimmy frasche, Robert Engels, Jan Mercl, golang-nuts, Robert Griesemer
On Aug 21, 2020, at 00:56, Ian Lance Taylor <ia...@golang.org> wrote:
>
> No, the intent is that you would switch on the type parameter itself,
> not a value.
>
> func g[T C](v T) T {
> switch T {
> // the rest is the same
> }
> }
>
> Thanks for asking.

Oh, this clarifies my remaining murkiness about the type switches quite nicely. Thanks!

- Dave

burak serdar

unread,
Aug 21, 2020, 8:57:13 AM8/21/20
to Kurtis Rader, Ian Lance Taylor, Jan Mercl, golang-nuts, Robert Griesemer
The point I was trying to make is that "any" as a constraint is better
than "interface{}" as a constraint, but "interface{}" as a type is
better than "any" as a type.

interface{}, when used as a constraint, doesn't mean than the value
has to be an interface{}, it means the value can be anything.
interface{}, when used as a value, doesn't mean that the value can be
anything, it means that the value is an interface, and you have to get
the value from that interface. Different uses, different identifiers.

I would be more comfortable if "any" was only a constraint instead of
an alias for interface{}. If your code needs it, you can still define
it. But if it is there, people will use it even if they can do without
it.

That said, I think the generics design draft looks great.

Carla Pfaff

unread,
Aug 21, 2020, 9:43:51 AM8/21/20
to golang-nuts
On Friday, 21 August 2020 at 14:57:13 UTC+2 bbse...@gmail.com wrote:
interface{}, when used as a constraint, doesn't mean than the value
has to be an interface{}, it means the value can be anything.
interface{}, when used as a value, doesn't mean that the value can be
anything, it means that the value is an interface, and you have to get
the value from that interface. Different uses, different identifiers.
 
The same is true for "interface{String() string}" as a constraint and "interface{String() string}" as a type.
Does that mean that you want to allow the identifier "fmt.Stringer" only for constraints, but not for types?

Ian Lance Taylor

unread,
Aug 21, 2020, 10:24:36 AM8/21/20
to Axel Wagner, golang-nuts
On Fri, Aug 21, 2020, 12:37 AM 'Axel Wagner' via golang-nuts <golan...@googlegroups.com> wrote:
Just to clarify, the intent is to make the declaration in the spec `type any = interface{}`, not `type any interface{}`, correct? The latter would be more analogous to `error`. Either has certain advantages and disadvantages, I'm not sure which I prefer, but I just want to make sure I understand the plan :)

I've been thinking of a type alias rather than a defined type, but I'm not sure which is best.  It would be interesting to hear whether anybody has a clear preference, and why.

Ian

Ian Lance Taylor

unread,
Aug 21, 2020, 10:28:40 AM8/21/20
to Bakul Shah, golang-nuts, Robert Griesemer
Right.  And, to be clear, that is how the current design draft works before this change.

Ian

roger peppe

unread,
Aug 21, 2020, 10:39:04 AM8/21/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
This is great. I like the fact that the type parameter list is just a normally formed parameter list now.
I did quite like the fact that you could see defined types with a grep for "type", but that's
not always the case anyway with type blocks, so I'll put that feeling aside.

2.

We’re going to simplify the rule for type list satisfaction.  The type
argument will satisfy the constraint if the type argument is identical
to any type in the type list, or if the underlying type of the type
argument is identical to any type in the type list.  What we are
removing here is any use of the underlying types of the types in the
type list.  This tweaked rule means that the type list can decide
whether to accept an exact defined type, other than a predeclared
type, or whether to accept any type with a matching underlying type.

This is a subtle change that we don’t expect to affect any existing
experimental code.

We think that this definition might work if we permit interface types
with type lists to be used outside of type constraints.  Such
interfaces would effectively act like sum types. That is not part of
this design draft, but it’s an obvious thing to consider for the
future.

Note that a type list can mention type parameters (that is, other type
parameters in the same type parameter list).  These will be checked by
first replacing the type parameter(s) with the corresponding type
argument(s), and then using the rule described above.

This is also a great forward-looking change.
 

3.

We’re going to clarify that when considering the operations permitted
for a value whose type is a type parameter, we will ignore the methods
of any types in the type list.  The general rule is that the generic
function can use any operation permitted by every type in the type
list.  However, this will only apply to operators and predeclared
functions (such as "len" and "cap").  It won’t apply to methods, for
the case where the type list includes a list of types that all define
some method.  Any methods must be listed separately in the interface
type, not inherited from the type list.

This rule seems generally clear, and avoids some complex reasoning
involving type lists that include structs with embedded type
parameters.

Ditto.
 

4.

We’re going to permit type switches on type parameters that have type
lists, without the “.(type)” syntax.  The “(.type)” syntax exists to
clarify code like “switch v := x.(type)”.  A type switch on a type
parameter won’t be able to use the “:=” syntax anyhow, so there is no
reason to require “.(type)”.  In a type switch on a type parameter
with a type list, every case listed must be a type that appears in the
type list (“default” is also permitted, of course).  A case will be
chosen if it is the type matched by the type argument, although as
discussed above it may not be the exact type argument: it may be the
underlying type of the type argument.  To make that rule very clear,
type switches will not be permitted for type parameters that do not
have type lists.  It is already possible to switch on a value “x”
whose type is a type parameter without a type list by writing code
like “switch (interface{})(x).(type)” (which may now be written as
“switch any(x).(type)”).  That construct is not the simplest, but it
uses only features already present in the language, and we don’t
expect it to be widely needed.

I'm less keen on this restriction. That latter construct has at least one significant
pitfall: if the type parameter is an interface type and x is nil, the type switch won't
choose the correct type. Instead, you'd need to do something like “switch (interface{})(&x).(type)”
and use the pointer type in the type switch (https://go2goplay.golang.org/p/IdIuWkMTaBF).
It might not even be obvious that we're switching on an interface type when we're using
a type parameter in the switch case. For example: https://go2goplay.golang.org/p/5ridyxPoJOZ.

Another issue with this approach is that you may need to use non-type-safe (and somewhat
verbose) code to work around it. Here's an example: https://go2goplay.golang.org/p/A-_mVRxhvl4
This fails because even though we know the type of the value in the type switch, the
compiler can't make the connection between that and the actual type of K, so
it's not possible to use values of the known type where values of type K are expected.
The workaround is to do a dynamic type conversion: https://go2goplay.golang.org/p/PBYDoESWXjW,
but it's not hard to get wrong, resulting in a runtime panic.

I suggest that we allow type switches on non-type-list type parameters and make it clear
that the type matching is exact - there would be no way to use a type switch to
match against a type parameter's underlying type.

  cheers,
    rog.


burak serdar

unread,
Aug 21, 2020, 10:46:22 AM8/21/20
to Carla Pfaff, golang-nuts
All constraints except "any" specify a constraint for the type. A
Stringer constraint will ensure that the type has String() string
method. "any" is a lack of constraint. I think the two concepts are
different. My problem is the attractiveness of "any" as a return type.
I can live with it, but I expect to see more questions raised about
functions returning "any" values behaving weird in a nil-check.


>
> --
> 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/4c9a2735-3e22-4568-ac0b-8c6a8b4b8583n%40googlegroups.com.

Chris Hines

unread,
Aug 21, 2020, 11:00:11 AM8/21/20
to golang-nuts
I believe these are fantastic choices.

I have given two presentations to Go meetups about the draft generics design in the last month. The second time was after the square bracket idea was added to the prototype and someone suggested the other parts adopted in item #1. In that second presentation I used the []'s syntax while walking through some examples and found it really helped the readability. In addition I was an instant fan of the [T any] syntax when it was suggested and I am happy to see that adopted. The rest of the tweaks are welcome as well, especially the addition of type switches on type parameters. I look forward to trying that out because I believe it plugs a hole in the design I had felt in my earlier experiments.

Well done and thanks to everyone who has helped provide feedback leading to these improvements.

Chris

Carla Pfaff

unread,
Aug 21, 2020, 1:02:17 PM8/21/20
to golang-nuts
On Friday, 21 August 2020 at 16:46:22 UTC+2 bbse...@gmail.com wrote:
All constraints except "any" specify a constraint for the type. A
Stringer constraint will ensure that the type has String() string
method. "any" is a lack of constraint.

The empty interface / any is a constraint that ensures that the type has at least 0 methods and all of these 0 methods must match the 0 methods of the interface. An empty purse is still a purse, an empty constraint is still a constraint.
 
My problem is the attractiveness of "any" as a return type. 

I don't see why anybody would find it attractive as a return type. People don't use the empty interface because they like it so much, but because Go doesn't have parametric polymorphism / "generics" yet. There are many programming languages that have a named top type and it is rarely abused. Programmers want to write type safe code if they can.

roger peppe

unread,
Aug 21, 2020, 1:09:06 PM8/21/20
to Ian Lance Taylor, Axel Wagner, golang-nuts
My vote is for a type alias. Using a named type doesn't make any difference for type parameter constraints, and I'd prefer it if "[]interface{}" and "[]any" (or other types involving interface{}) weren't incompatible types. "interface{}" is currently a nice universally understood type - I don't think it's worth splitting it into two distinct types.

  cheers,
    rog.




Ian

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

Jan Mercl

unread,
Aug 21, 2020, 1:28:48 PM8/21/20
to Carla Pfaff, golang-nuts
On Fri, Aug 21, 2020, 19:01 'Carla Pfaff' via golang-nuts <golan...@googlegroups.com> wrote:

People don't use the empty interface because they like it so much, but because Go doesn't have parametric polymorphism / "generics" yet.

This argument seems to fail the fmt.Printf and friends reality check.

Axel Wagner

unread,
Aug 21, 2020, 1:31:06 PM8/21/20
to roger peppe, Ian Lance Taylor, golang-nuts
My one concern with making it an alias is error messages.
If the source code says "any", I think so should the error messages. Currently, the compiler forgets aliases too early.

roger peppe

unread,
Aug 21, 2020, 1:45:17 PM8/21/20
to Axel Wagner, Ian Lance Taylor, golang-nuts
On Fri, 21 Aug 2020 at 18:30, Axel Wagner <axel.wa...@googlemail.com> wrote:
My one concern with making it an alias is error messages.
If the source code says "any", I think so should the error messages. Currently, the compiler forgets aliases too early.

I agree that's a concern, but I think that should be a reason to fix error messages when aliases are used, not to use a named type.

Denis Cheremisov

unread,
Aug 21, 2020, 1:54:40 PM8/21/20
to golang-nuts
BTW, I am really glad your proposal is accepted, now the whole thing feels polished and IMO it is time to start building an implementation.

пятница, 21 августа 2020 г. в 20:02:17 UTC+3, Carla Pfaff:

burak serdar

unread,
Aug 21, 2020, 2:15:46 PM8/21/20
to Carla Pfaff, golang-nuts
I disagree. Especially people coming from other languages with a
strong emphasis on DRY tend to overuse interface{}, many times
incorrectly, and generics will not fix that. "any" will make it more
attractive, because it no longer looks like an interface. I agree that
this is a hypothetical problem at this point without any actual
complaints from developers. But defining an alias "any" is easy if you
need it. Providing one predefined is endorsing its use.

>
> --
> 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/8e5b46ba-7a3e-4d55-ac97-1e70d06e622dn%40googlegroups.com.

Viktor Kojouharov

unread,
Aug 21, 2020, 3:51:47 PM8/21/20
to golang-nuts
I like all points in the draft change.

My 2 cents around the any alias would be that it shouldn't be a problem. I don't think people are suddently going to start overusing it, since using vaues of type `interface{}` is extremely tedious in Go, and that won't magically change if the type is shorter by a few characters.

One thing that might be of concern regarding using type lists in interfaces is that, if that is allowed outside of generics, then it should be decided beforehand whether a type switch over such values is exhaustive or not. I don't think it will be a breaking change if it is exhaustive for such interfaces, but if it isn't, then changing it later would be. For the record, I think it should b exhaustive.

Russ Cox

unread,
Aug 21, 2020, 4:28:18 PM8/21/20
to Axel Wagner, roger peppe, Ian Lance Taylor, golang-nuts
On Fri, Aug 21, 2020 at 1:30 PM 'Axel Wagner' via golang-nuts <golan...@googlegroups.com> wrote:
My one concern with making it an alias is error messages.
If the source code says "any", I think so should the error messages. Currently, the compiler forgets aliases too early.

Russ Cox

unread,
Aug 21, 2020, 4:54:28 PM8/21/20
to golang-nuts
Hi all,

A few people have raised concerns about "encouraging" use of any instead of interface{}, on the grounds that it is not idiomatic Go. I just wanted to acknowledge that yes, it's not idiomatic Go today, but that's true of essentially every language change. Thinking about future usage is a good consideration, but we define idiomatic by convention, and that convention is heavily influenced by what is and is not in the language. It is OK for the convention to shift, and here it almost certainly would.

Many years ago, there was no rune type in Go. We used int instead, as in:

    runes := []int("hello")

It would certainly not have been idiomatic at the time to instead define

    type rune = int

and then write code like:

    runes := []rune("hello")

The argument against doing this would be that everyone expects to see []int instead, and that introducing your own custom definition here for a standard language concept adds to the conceptual burden for readers by making your code look different from everyone else's, forcing readers to search out the definition of "rune" to understand the code. (It was equally non-idiomatic to define "type error = os.Error" before we had the predefined error type.)

The same argument that applied back then to a custom rune = int or error = os.Error alias applies today to a custom any = interface{} alias: it's not a good thing to make your code gratuitously different from others' code.

But just as "type rune = int" being non-idiomatic did not preclude introducing a standard predefined rune type alias, the fact that "type any = interface{}" is non-idiomatic today does not preclude introducing a standard predefined any type alias. It becomes idiomatic by making it part of the language. Then everyone can use it, and no one's code is gratuitously different. 

It's also true that older Go code using interface{} will look different from newer Go code using "any". But that is true of every language change. New code that omits semicolons looks different from old code that doesn't. New code that writes 0o777 looks different from old code that writes 0777. And so on, for every change we make. This is fundamental to languages evolving: new code and old code look different. (Part of minimizing the need to code switch is providing tools to help update old code to look new; we've done that repeatedly in the past and would undoubtedly do that here as well.)

A few other people have raised concerns about not seeing the word interface and therefore not realizing "any" is an interface type and potentially getting confused. This is also a good consideration, but we already have many interface types that don't use the word interface, most notably the predefined type error, but also io.Reader, http.ResponseWriter, and so on. People learn early on that not all interface types say interface in the name. I don't expect that "any" will not be any harder to learn than the others.

In any (ha!) event, it's probably more important to focus on the larger semantics of generics than this specific color, attractive though it may be.

Best,
Russ

roger peppe

unread,
Aug 21, 2020, 5:08:26 PM8/21/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
We’re going to simplify the rule for type list satisfaction.  The type

Here's one interesting implication of this: it allows us to do type conversions that were not previously possible.

For example, if we have "type I int", we can use a type switch to convert some type []I to type []int:

func F[type T intlike](ts []T) []int {
    switch T {
    case int:
        return ts
    }
    return nil
}

It seems to me that this kind of thing will allow us to perform a similar conversion (convert some part of the type to its underlying type) on any type.

In the early days of Go, the spec allowed this kind of conversion as a normal type conversion. I wonder if it might be reasonable to revert to those more relaxed semantics. I think they're potentially useful, for example, when dealing with named types obtained from modules with two different major versions without incurring copies.

Although in the above-linked issue Robert talks about runtime costs such as "possibly re-mapping method tables", I don't see that this would necessarily be the case. Thoughts?

jimmy frasche

unread,
Aug 21, 2020, 5:11:48 PM8/21/20
to roger peppe, Ian Lance Taylor, golang-nuts, Robert Griesemer
I'd assume that would fail to compile as you're returning a []T not a []int
> --
> 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/CAJhgacjL7p7qck%3DSO0Nz9f%2BKZw6MNcgkD5REXwSNK7_fCTXYQg%40mail.gmail.com.

roger peppe

unread,
Aug 21, 2020, 5:31:01 PM8/21/20
to jimmy frasche, Ian Lance Taylor, golang-nuts, Robert Griesemer
On Fri, 21 Aug 2020 at 22:10, jimmy frasche <soapbo...@gmail.com> wrote:
I'd assume that would fail to compile as you're returning a []T not a []int

If that's the case, then I'm not sure that such a type switch would be very useful. It would tell you what type the values are, but you can't do anything with them because all the values would still be of the original type.

I had assumed that the intention was that within the arm of the type switch, the switched type would take on the specified type.
That would allow (for example) specialising to use underlying machine operations on []T when T is a known type such as byte.

Axel Wagner

unread,
Aug 21, 2020, 5:43:03 PM8/21/20
to roger peppe, jimmy frasche, Ian Lance Taylor, golang-nuts, Robert Griesemer
On Fri, Aug 21, 2020 at 11:30 PM roger peppe <rogp...@gmail.com> wrote:
On Fri, 21 Aug 2020 at 22:10, jimmy frasche <soapbo...@gmail.com> wrote:
I'd assume that would fail to compile as you're returning a []T not a []int

If that's the case, then I'm not sure that such a type switch would be very useful. It would tell you what type the values are, but you can't do anything with them because all the values would still be of the original type.

You can reasonably convert them to their underlying type and *then* use them as such.
That would make it useful while not allowing what you posit.

I had assumed that the intention was that within the arm of the type switch, the switched type would take on the specified type.
That would allow (for example) specialising to use underlying machine operations on []T when T is a known type such as byte.

It would, however, prevent you from calling methods on the type or pass it to a function taking an interface compatible with the constraint.
Also, I shudder to even imagine how this could be put into a spec.
 

Axel Wagner

unread,
Aug 21, 2020, 5:44:03 PM8/21/20
to roger peppe, jimmy frasche, Ian Lance Taylor, golang-nuts, Robert Griesemer
also, of course, you could still use operators with them, while now also knowing the exact semantics of those operators (e.g. in regards to overflow), which might also be useful.