How the scopes of type parameters are defined?

147 views
Skip to first unread message

tapi...@gmail.com

unread,
Nov 12, 2021, 3:48:03 AM11/12/21
to golang-nuts
I expect the following code compilers okay, but it doesn't.
It looks All the three "I" in the Bar function declaration are
viewed as the type parameter, whereas the second one is
expected as the constraint I (at least by me).

package main

type I interface { M() }

func Foo(i I) {
  i.M()
}

func Bar[I I](i I) { // cannot use a type parameter as constraint
  i.M()
}

func main() {}

Axel Wagner

unread,
Nov 12, 2021, 4:33:19 AM11/12/21
to golang-nuts
I suspect this issue is hard/impossible to avoid, because it must be possible to self- and mutually reference type-parameters, in a way that it's not for normal parameters, to allow to write something like

type Equaler[T any] interface {
    Equal(T) bool
}
func Eq[T Equaler[T]](a, b T) bool {
    return a.Equal(b)
}

or basically any use-case for constraint-type inference.

Even if we *could* allow it consistently, the rules for doing that would likely be pretty complex. Better to take the relatively minor hit of disallowing this overloading (you can always use different names for the type-parameters, if they conflict with the constraint you want to use) than to take the hit of complex scoping rules with lots of exceptions.

--
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/9fc66e8e-3f99-4c3a-87f7-ce7c1a705cdcn%40googlegroups.com.

tapi...@gmail.com

unread,
Nov 12, 2021, 4:53:32 AM11/12/21
to golang-nuts
On Friday, November 12, 2021 at 5:33:19 PM UTC+8 axel.wa...@googlemail.com wrote:
I suspect this issue is hard/impossible to avoid, because it must be possible to self- and mutually reference type-parameters, in a way that it's not for normal parameters, to allow to write something like

type Equaler[T any] interface {
    Equal(T) bool
}
func Eq[T Equaler[T]](a, b T) bool {
    return a.Equal(b)
}

I don't see the same problem here. "T" and "Equaler" are two different identifiers.

Axel Wagner

unread,
Nov 12, 2021, 5:04:01 AM11/12/21
to tapi...@gmail.com, golang-nuts
On Fri, Nov 12, 2021 at 10:54 AM tapi...@gmail.com <tapi...@gmail.com> wrote:


On Friday, November 12, 2021 at 5:33:19 PM UTC+8 axel.wa...@googlemail.com wrote:
I suspect this issue is hard/impossible to avoid, because it must be possible to self- and mutually reference type-parameters, in a way that it's not for normal parameters, to allow to write something like

type Equaler[T any] interface {
    Equal(T) bool
}
func Eq[T Equaler[T]](a, b T) bool {
    return a.Equal(b)
}

I don't see the same problem here. "T" and "Equaler" are two different identifiers.

But T and T are not.

The point I was making is that type-parameters must inherently be scoped in a way that makes them visible in the type-list itself, to make writing code like this possible. This isn't necessary for regular parameters and it's what makes type-parameter lists special.

I *also* said that it's probably possible to devise rules which would make this possible, while allowing the example you wrote, just that those rules would have to be complicated.
 
 

or basically any use-case for constraint-type inference.

Even if we *could* allow it consistently, the rules for doing that would likely be pretty complex. Better to take the relatively minor hit of disallowing this overloading (you can always use different names for the type-parameters, if they conflict with the constraint you want to use) than to take the hit of complex scoping rules with lots of exceptions.

On Fri, Nov 12, 2021 at 9:48 AM tapi...@gmail.com <tapi...@gmail.com> wrote:
I expect the following code compilers okay, but it doesn't.
It looks All the three "I" in the Bar function declaration are
viewed as the type parameter, whereas the second one is
expected as the constraint I (at least by me).

package main

type I interface { M() }

func Foo(i I) {
  i.M()
}

func Bar[I I](i I) { // cannot use a type parameter as constraint
  i.M()
}

func main() {}

--
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/9fc66e8e-3f99-4c3a-87f7-ce7c1a705cdcn%40googlegroups.com.

--
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,
Nov 12, 2021, 5:07:11 AM11/12/21
to golang-nuts
And FWIW, just to illustrate the expected complexity: We also want these two declarations to mean the same:
func A[T A]()
func B[T interface{ A }]()
So the rules also need to accommodate that. It gets only more complicated from there.
Message has been deleted
Message has been deleted
Message has been deleted

T L

unread,
Nov 12, 2021, 8:41:05 AM11/12/21
to Axel Wagner, golang-nuts
Are the followings also intended?

func Foo[T any](T T) T { // (the 2nd) T redeclared in this block
   return T // T (type) is not an expression
}

func Bar[T any](x T) T {
   var T = x // T redeclared in this block
   return T
}

T L

unread,
Nov 12, 2021, 8:44:05 AM11/12/21
to Axel Wagner, golang-nuts
It is strange that my recent comments are deleted automatically if they are posted from the web interface.
But now from the email interface.

T L

unread,
Nov 12, 2021, 8:55:34 AM11/12/21
to Axel Wagner, golang-nuts
This one compiles okay:

func Bar2[T any](x T) T {

   {
     var T = x // T redeclared in this block
     return T
   }
}

So the parameter type is declared at the local top block?

Axel Wagner

unread,
Nov 12, 2021, 10:21:47 AM11/12/21
to golang-nuts
It's hard to say exactly, at this point, because the spec-changes for generics are not yet written.
I don't think the behavior you observe is bad, but we'll have to see how we can put it into the spec.

Currently, function parameters are scoped to the function body. Uniqueness is litigated via a separate rule. That explains why `func(x int) { var x string }` complains about a re-declaration - both `x` are scoped to the function body. With `func(x int) { { var x string } }`, the variable-declaration is scoped to the inner block, while the parameter is scoped to the body itself. This difference explains why your last example compiles, but the previous doesn't.

The solution for type-parameters will probably be similar - they get scoped to the function body (or type-literal), but get some extra rules for how they are resolved inside the function signature itself. That could relatively easily explain all the behavior you observe.


Axel Wagner

unread,
Nov 12, 2021, 12:23:29 PM11/12/21
to tapi...@gmail.com, golang-nuts


On Fri, Nov 12, 2021 at 6:20 PM tapi...@gmail.com <tapi...@gmail.com> wrote:
How much complicated? Is it similar to

    func foo(int int) {}

No. As I said, the situation is different, as type-parameters must be able to be self- and mutually referential.
 

Ian Lance Taylor

unread,
Nov 12, 2021, 4:50:18 PM11/12/21
to Axel Wagner, tapi...@gmail.com, golang-nuts
On Fri, Nov 12, 2021 at 9:23 AM 'Axel Wagner' via golang-nuts
<golan...@googlegroups.com> wrote:
>
> On Fri, Nov 12, 2021 at 6:20 PM tapi...@gmail.com <tapi...@gmail.com> wrote:
>>
>> How much complicated? Is it similar to
>>
>> func foo(int int) {}
>
>
> No. As I said, the situation is different, as type-parameters must be able to be self- and mutually referential.

The scope of regular parameters starts at the beginning of the
function body (https://golang.org/ref/spec#Declarations_and_scope,
rule 4 in the list of rules).

The scope of a type parameter starts at the beginning of the type
parameter list (https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#mutually-referencing-type-parameters).

Ian

Axel Wagner

unread,
Nov 12, 2021, 6:04:44 PM11/12/21
to Ian Lance Taylor, tapi...@gmail.com, golang-nuts
Just to nitpick, for eventual consideration when writing the spec. This would, AIUI, imply that one of the above examples should compile, namely:

func F[T any]() {
    const T = 42
}

If there is a new, implicit block surrounding the func-declaration (similar to how we do it for if/for/switch) which type-parameters are bound to, the function body should be a *new* scope and re-declaring T inside that new scope should be valid, as it's more deeply nested.

I don't think it should be valid, just that the most straight forward way to put this into rules is somewhat subtle.

 

Ian
Reply all
Reply to author
Forward
0 new messages