Type declaration inside function

289 views
Skip to first unread message

havard...@gmail.com

unread,
Aug 17, 2015, 6:56:02 PM8/17/15
to golang-nuts
I'm trying to understand why the following is accepted by gc (http://play.golang.org/p/oLyvBz1DvO):

package main

type (
    A struct{ *B }
    B struct{ *A }
)

func main() {}

while this is not allowed (http://play.golang.org/p/mwRO6RP4be):

package main

func main() {
    type (
        A struct{ *B }
        B struct{ *A }
    )
}

The latter makes gc report "main.go:5: undefined: B".

Is this behavour implied from the language spec?

-Håvard

Rob Pike

unread,
Aug 17, 2015, 7:08:30 PM8/17/15
to havard...@gmail.com, golang-nuts
Briefly, this is because order of declaration matters inside a function but does not outside. At package scope, the symbols are declared in the order that satisfies their dependencies independent of their appearance on the page; inside a function, they are declared in lexical order. A moment's reflection will show why this inconsistency has merit, although it is, well, inconsistent.

-rob


--
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.
For more options, visit https://groups.google.com/d/optout.

James Aguilar

unread,
Aug 17, 2015, 10:55:44 PM8/17/15
to golang-nuts, havard...@gmail.com
I can think of a lot of different reasons, but I'm not sure which one is the one the go authors have decided has the most merit. The main reasons I thought were:

1. People think of functions as proceeding forward, thus we will impart that semantic behavior on types declared in a function, whether it's necessary or not. In other words, we have to choose whether we're being inconsistent with "functions move forward" or "types can be declared in arbitrary order," and we chose to be inconsistent with the latter.
2. As an opinionated way to discourage users from making complicated type graphs in functions?
3. So that the same dependency code can be used for all statements and declarations within a function -- even in types where it is not strictly necessary. In other words, as a way to reduce toolchain code complexity.

But I don't know which of these, or another option, is actually right. Could we get a hint? Particularly, is there a case I haven't thought of where it is actually not possible to ignore the order of types declared in a function?

Ian Lance Taylor

unread,
Aug 18, 2015, 12:03:19 AM8/18/15
to James Aguilar, golang-nuts, havard...@gmail.com
On Mon, Aug 17, 2015 at 7:55 PM, James Aguilar <aguila...@gmail.com> wrote:
>
> I can think of a lot of different reasons, but I'm not sure which one is the
> one the go authors have decided has the most merit. The main reasons I
> thought were:
>
> 1. People think of functions as proceeding forward, thus we will impart that
> semantic behavior on types declared in a function, whether it's necessary or
> not. In other words, we have to choose whether we're being inconsistent with
> "functions move forward" or "types can be declared in arbitrary order," and
> we chose to be inconsistent with the latter.
> 2. As an opinionated way to discourage users from making complicated type
> graphs in functions?
> 3. So that the same dependency code can be used for all statements and
> declarations within a function -- even in types where it is not strictly
> necessary. In other words, as a way to reduce toolchain code complexity.
>
> But I don't know which of these, or another option, is actually right. Could
> we get a hint? Particularly, is there a case I haven't thought of where it
> is actually not possible to ignore the order of types declared in a
> function?

It would be hard to understand a function if var and const
declarations did not occur in declaration order. Each var and const
declaration defines a name that can then only be referenced later in
the function. Any initialization in a var statement occurs in
declaration order. I think anything else would be quite confusing.

Given that, it's hard to argue that types within a function should not
also be defined in declaration order. Type declarations look very
much like var declarations. Having them be different within a
function would add still another layer of inconsistency.

Ian

Steven Blenkinsop

unread,
Aug 18, 2015, 4:03:15 PM8/18/15
to Ian Lance Taylor, James Aguilar, golang-nuts, havard...@gmail.com
Constants and types technically could behave like labels within a function, I think, in that they are in scope for the entire function body without any reference to order. Functions are a bit harder, assuming they can act as closures, but intuition could again be drawn from labels, by making the requirement be that you can't call a function that is declared either inside a nested scope or somewhere where there a variables in scope which aren't in scope at the call site.

Not to suggest that these would necessarily be worthwhile to allow this. I'm just exploring how to make it at least makes sense.

Scott Pakin

unread,
Aug 18, 2015, 6:55:05 PM8/18/15
to golang-nuts, aguila...@gmail.com, havard...@gmail.com
On Monday, August 17, 2015 at 10:03:19 PM UTC-6, Ian Lance Taylor wrote:
It would be hard to understand a function if var and const
declarations did not occur in declaration order.  Each var and const
declaration defines a name that can then only be referenced later in
the function.  Any initialization in a var statement occurs in
declaration order.  I think anything else would be quite confusing.

Given that, it's hard to argue that types within a function should not
also be defined in declaration order.  Type declarations look very
much like var declarations.  Having them be different within a
function would add still another layer of inconsistency. 

Perhaps a more reasonable solution would be for Go to allow forward declarations of types.  This would allow them to remain in declaration order while still supporting the definition of mutually recursive types:

type B
type A struct{ *B }
type B struct{ *A }

Just a thought,
— Scott

James Aguilar

unread,
Aug 18, 2015, 8:07:09 PM8/18/15
to golang-nuts, aguila...@gmail.com, havard...@gmail.com
On Tuesday, August 18, 2015 at 3:55:05 PM UTC-7, Scott Pakin wrote:
Perhaps a more reasonable solution would be for Go to allow forward declarations of types.  This would allow them to remain in declaration order while still supporting the definition of mutually recursive types:

type B
type A struct{ *B }
type B struct{ *A }

Just a thought,
— Scott

It isn't worth adding predeclarations just to get this little bit of syntactic sugar. 

Thomas Bushnell, BSG

unread,
Aug 18, 2015, 8:09:41 PM8/18/15
to James Aguilar, golang-nuts, havard...@gmail.com
Well, technically it isn't sugar, since there is no other way to build mutually recursive data structures with local scope. But I agree it's not very important.

--

Steven Blenkinsop

unread,
Aug 18, 2015, 8:59:57 PM8/18/15
to Thomas Bushnell, BSG, James Aguilar, golang-nuts, havard...@gmail.com
Which is why I was suggesting using the label scope rules. It doesn't require adding predeclarations. It would be a breaking change, though, unfortunately.

chris dollin

unread,
Aug 19, 2015, 10:25:02 AM8/19/15
to Scott Pakin, golang-nuts, aguila...@gmail.com, havard...@gmail.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.
> For more options, visit https://groups.google.com/d/optout.



--
Chris "allusive" Dollin

chris dollin

unread,
Aug 19, 2015, 10:34:06 AM8/19/15
to Scott Pakin, golang-nuts, aguila...@gmail.com, havard...@gmail.com
On 19 August 2015 at 15:24, chris dollin <ehog....@googlemail.com> wrote:

>> Perhaps a more reasonable solution would be for Go to allow forward
>> declarations of types. This would allow them to remain in declaration order
>> while still supporting the definition of mutually recursive types:

Why not just allow sequences of declarations inside a
function to be mutually in scope just as the ones outside
are? Evaluation would be directed by dependency order,
same as for outside a function.

(A breaking change but it would be interesting to know /how
much/ breaking it was.)

(For these purposes, := wouldn't count as a declaration,
just as it isn't a declaration outside functions (by virtue of
not being allowed there at all ...))

(This would also allow eg

var z = x + y
var x = 1
var y = 2

which I'm pretty sure would be too exotic but can
read nicely as a where-declaration; the compiler
could object to non-circular declarations with
the dependencies pointing the wrong way.)

Chris

--
Chris "allusive" Dollin

Nico

unread,
Aug 19, 2015, 10:56:21 AM8/19/15
to golan...@googlegroups.com
The alternative to this syntactic sugar is a rather ugly use of anonymous types:

// http://play.golang.org/p/BYS4bdxxfM

package main

func main() {
type A struct{ B *struct{ *A } }

var a A
var b struct{ *A }

a.B = &b
b.A = &a
}

Nico

unread,
Aug 19, 2015, 11:05:26 AM8/19/15
to golan...@googlegroups.com
An alternative that requires no grammar changes would be that an single statement like:

type (
A struct{ *B }
B struct{ *A }
)

would work as expected, whereas two separate statements like these:

type A struct{ *B }
type B struct{ *A }

wouldn't.

Bakul Shah

unread,
Aug 19, 2015, 11:08:45 AM8/19/15
to Nico, golan...@googlegroups.com
May be type {...} can be used for mutually recursive types? {...} to delimit the scope of recursion.

type { a struct { x *b }; b struct { y *a } }
Reply all
Reply to author
Forward
0 new messages