Why does the Go language put type declarations at the end?

  • 2020-06-23 00:39:32
  • OfStack

As for types, there is a section on the official website which introduces the function pointer in detail. The current design is much clearer than C's syntax.

That is, the type is placed after it for clarity. Rob Pike once explained this problem on the official Go blog (see: Go 's Declaration Syntax). The translation is as follows:

The introduction

Newcomers to Go often wonder why the language's declarative syntax (declaration syntax) differs from that of the traditional C family of languages. In this post, we'll make a comparison and answer it.

The grammar of the C

First, take a look at C's syntax. C USES a clever and unusual declarative syntax. To declare a variable, simply write an expression with the name of the target variable, and then specify the type of the expression itself. Such as:


int x;

The above code declares the x variable and its type is int -- that is, the expression x is of type int. In general, in order to specify the type of a new variable, we need to write an expression that contains the variable we want to declare. The resulting value of the expression belongs to a primitive type that we write to the left of the expression. Therefore, the following statement:


int *p;
int a[3];

Indicates that p is a pointer of type int, because *p is of type int. a, on the other hand, is an int array because a[3] is of type int (never mind the index value shown here, it is just used to indicate the length of the array).

Let's look at function declarations. The type of the argument in the C function declaration is written outside the parentheses, as follows:


int main(argc, argv)
  int argc;
  char *argv[];
{ /* ... */ }

As mentioned earlier, we can see that main is a function because the expression main(argc, argv) returns int. In modern notation we write it this way:


int main(int argc, char *argv[]) { /* ... */ }

Although it looks a little different, the basic structure is one.

In general, C's syntax is clever when the types are simple. Unfortunately, C's syntax quickly becomes confusing once the types start to get complicated. For famous examples such as function Pointers, write as follows:


int (*fp)(int a, int b);

Here, fp is a pointer because if you write an expression like (*fp)(a, b) it will call a function that returns a value of type int. What if one of the arguments to fp is itself a function?


int (*fp)(int (*ff)(int x, int y), int b)

It's a little harder to read.

Of course, we can declare the function without specifying the name of the parameter, so the main function can be declared as:


int main(int, char *[])

Recall that argv looked like this before


char *argv[]

Did you notice that you removed the variable name from the declared "middle" and constructed the type of the variable? It's not obvious, but when you declare a variable of type char *[], you need to insert the name into the middle of the variable type.

Let's look again at what happens if we don't name the parameter fp:


int (*fp)(int (*)(int, int), int)

The hard thing about this stuff is not just remembering that the parameter names are supposed to be in the middle


int (*)(int, int)

What makes it even more confusing is that it may not even be clear that this is a function pointer declaration. Let's see, what if the return value is also a function pointer type


int *p;
int a[3];
0

It's hard to see this as a statement about fp.

You can build more complex examples on your own, but this is enough to explain some of the complexity introduced by C's declarative syntax.

One more thing to note is that since the type syntax is the same as the declaration syntax, parsing an expression with a type in the middle can be difficult. That's why C always puts the type in parentheses when it casts, like this


(int)M_PI

The grammar of the Go

Languages other than the C family typically use a different type syntax for declarations. Usually the name comes first and is often followed by a colon. If we write it this way, our example above will look like this:


x: int
p: pointer to int
a: array[3] of int

Such a statement, if lengthy, is at least clear -- you just have to read it from left to right. Go's approach is based on this, but in the pursuit of simplicity, Go has dropped the colon and removed some key words to make it look like this:


int *p;
int a[3];
3

There is no direct correspondence between [3]int and the use of a in the expression (we'll come back to Pointers in the next section). At this point, you gain code clarity, but at the cost of syntactic discrimination.

Now let's think about functions. Although in Go, the main function actually has no arguments, let's copy the declaration of the main function before 1:


int *p;
int a[3];
4

At first glance, it looks like C, but reading from left to right is fine.

The main function takes 1 int and 1 pointer and returns 1 int.

If you drop the parameter name at this point, it's still clear -- because the parameter name always comes before the type, there's no confusion.


int *p;
int a[3];
5

One value of this left-to-right style of declaration is that as types get more complex, they remain relatively simple. Here is a declaration of a function variable (equivalent to a function pointer in C)


int *p;
int a[3];
6

Or when it returns a function:


f func(func(int,int) int, int) func(int, int) int

The above statement reads clearly, from left to right, and it's easy to see which variable name is currently being declared -- the variable name is always in the first place.

The difference between type syntax and expression syntax also makes it easier to invoke closures in Go:


sum := func(a, b int) int { return a+b } (3, 4)

Pointer to the

There are some exceptions to Pointers. Note that in arrays (array) and slices (slice), the type syntax of Go puts the brackets to the left of the type, but in expression syntax it puts the brackets to the right:


int *p;
int a[3];
9

Similarly, Go's pointer follows C's * notation, but we also write the * to the right of the variable name when we declare it, but put the * to the left of the expression:


var p *int
x = *p

I can't write it like this


var p *int
x = p*

Since the suffix * can be confused with multiplication, maybe we can use the Pascal ^ notation, like this


var p ^int
x = p^

We might actually want to change * to the ^ as above, because * prefixes in types and expressions do complicate things a bit, for example, though we could write it like this


[]int("hi")

But when converting, if the type begins with *, parentheses are used:


(*int)(nil)

If there is a day when we are willing to give up * as a pointer syntax, then the parentheses above can be omitted.

As you can see, the pointer syntax of Go is similar to C. But this similarity also means that we cannot completely avoid situations in which parentheses are sometimes required in grammar to avoid ambiguity in types and expressions.

All in all, despite its shortcomings, we believe that Go's type syntax is easier to understand than C's. Especially if the type is complex.

conclusion


Related articles: