Go's Declaration Syntax go语言声明语法

7 July 2010

Introduction

Newcomers to Go wonder why the declaration syntax is different from the tradition established in the C family. In this post we'll compare the two approaches and explain why Go's declarations look as they do.

C syntax

First, let's talk about C syntax. C took an unusual and clever approach to declaration syntax. Instead of describing the types with special syntax, one writes an expression involving the item being declared, and states what type that expression will have. Thus

int x;

declares x to be an int: the expression 'x' will have type int. In general, to figure out how to write the type of a new variable, write an expression involving that variable that evaluates to a basic type, then put the basic type on the left and the expression on the right.

Thus, the declarations

int *p;
int a[3];

state that p is a pointer to int because '*p' has type int, and that a is an array of ints because a[3] (ignoring the particular index value, which is punned to be the size of the array) has type int.

What about functions? Originally, C's function declarations wrote the types of the arguments outside the parens, like this:

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

Again, we see that main is a function because the expression main(argc, argv) returns an int. In modern notation we'd write

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

but the basic structure is the same.

This is a clever syntactic idea that works well for simple types but can get confusing fast. The famous example is declaring a function pointer. Follow the rules and you get this:

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

Here, fp is a pointer to a function because if you write the expression (*fp)(a, b) you'll call a function that returns int. What if one of fp's arguments is itself a function?

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

That's starting to get hard to read.

Of course, we can leave out the name of the parameters when we declare a function, so main can be declared

int main(int, char *[])

Recall that argv is declared like this,

char *argv[]

so you drop the name from the middle of its declaration to construct its type. It's not obvious, though, that you declare something of type char *[] by putting its name in the middle.

And look what happens to fp's declaration if you don't name the parameters:

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

Not only is it not obvious where to put the name inside

int (*)(int, int)

it's not exactly clear that it's a function pointer declaration at all. And what if the return type is a function pointer?

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

It's hard even to see that this declaration is about fp.

You can construct more elaborate examples but these should illustrate some of the difficulties that C's declaration syntax can introduce.

There's one more point that needs to be made, though. Because type and declaration syntax are the same, it can be difficult to parse expressions with types in the middle. This is why, for instance, C casts always parenthesize the type, as in

(int)M_PI

Go syntax

Languages outside the C family usually use a distinct type syntax in declarations. Although it's a separate point, the name usually comes first, often followed by a colon. Thus our examples above become something like (in a fictional but illustrative language)

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

These declarations are clear, if verbose - you just read them left to right. Go takes its cue from here, but in the interests of brevity it drops the colon and removes some of the keywords:

x int
p *int
a [3]int

There is no direct correspondence between the look of [3]int and how to use a in an expression. (We'll come back to pointers in the next section.) You gain clarity at the cost of a separate syntax.

Now consider functions. Let's transcribe the declaration for main as it would read in Go, although the real main function in Go takes no arguments:

func main(argc int, argv []string) int

Superficially that's not much different from C, other than the change from char arrays to strings, but it reads well from left to right:

function main takes an int and a slice of strings and returns an int.

Drop the parameter names and it's just as clear - they're always first so there's no confusion.

func main(int, []string) int

One merit of this left-to-right style is how well it works as the types become more complex. Here's a declaration of a function variable (analogous to a function pointer in C):

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

Or if f returns a function:

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

It still reads clearly, from left to right, and it's always obvious which name is being declared - the name comes first.

The distinction between type and expression syntax makes it easy to write and invoke closures in Go:

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

Pointers

Pointers are the exception that proves the rule. Notice that in arrays and slices, for instance, Go's type syntax puts the brackets on the left of the type but the expression syntax puts them on the right of the expression:

var a []int
x = a[1]

For familiarity, Go's pointers use the * notation from C, but we could not bring ourselves to make a similar reversal for pointer types. Thus pointers work like this

var p *int
x = *p

We couldn't say

var p *int
x = p*

because that postfix * would conflate with multiplication. We could have used the Pascal ^, for example:

var p ^int
x = p^

and perhaps we should have (and chosen another operator for xor), because the prefix asterisk on both types and expressions complicates things in a number of ways. For instance, although one can write

[]int("hi")

as a conversion, one must parenthesize the type if it starts with a *:

(*int)(nil)

Had we been willing to give up * as pointer syntax, those parentheses would be unnecessary.

So Go's pointer syntax is tied to the familiar C form, but those ties mean that we cannot break completely from using parentheses to disambiguate types and expressions in the grammar.

Overall, though, we believe Go's type syntax is easier to understand than C's, especially when things get complicated.

Notes

Go's declarations read left to right. It's been pointed out that C's read in a spiral! See The "Clockwise/Spiral Rule"by David Anderson.

By Rob Pike

Related articles

14 Go's Declaration Syntax go语言声明语法的更多相关文章

  1. 如何读懂复杂的C语言声明

    本文已迁移至: http://www.danfengcao.info/c/c++/2014/02/25/howto-understand-complicated-declaration-of-c.ht ...

  2. 如何解析复杂的C语言声明

    C语言中有时会出现复杂的声明,比如   char * const * (*next) (); //这是个什么东东?   在讲复杂声明的分析方法前,先来个补充点.   C语言变量的声明始终贯彻两点 :  ...

  3. C语言声明解析方法

    1.C语言声明的单独语法成份     声明器是C语言声明的非常重要成份,他是所有声明的核心内容,简单的说:声明器就是标识符以及与它组合在一起的任何指针.函数括号.数组下表等,为了方便起见这里进行分类表 ...

  4. [C语言]声明解析器cdecl修改版

    一.写在前面 K&R曾经在书中承认,"C语言声明的语法有时会带来严重的问题.".由于历史原因(BCPL语言只有唯一一个类型——二进制字),C语言声明的语法在各种合理的组合下 ...

  5. Why Go's Declaration Syntax is better than C++?

    [Why Go's Declaration Syntax is better than C++?] Newcomers to Go wonder why the declaration syntax ...

  6. Go's Declaration Syntax

    Introduction Newcomers to Go wonder why the declaration syntax is different from the tradition estab ...

  7. 02. Go 语言基本语法

    Go语言基本语法 变量.数据类型和常量是编程中最常见,也是很好理解的概念.本章将从 Go 语言的变量开始,逐步介绍各种数据类型及常量. Go 语言在很多特性上和C语言非常相近.如果读者有C语言基础,那 ...

  8. Java语言基本语法

    Java语言基本语法 一.标识符和关键字 标识符 在java语言中,用来标志类名.对象名.变量名.方法名.类型名.数组名.包名的有效字符序列,称为“标识符”: 标识符由字母.数字.下划线.美元符号组成 ...

  9. C语言基础语法

    #include <stdio.h> int main() { int age; printf("input your age"); scanf("%d&qu ...

随机推荐

  1. BZOJ4036 [HAOI2015]按位或 【minmax容斥 + 期望 + FWT】

    题目链接 BZOJ4036 题解 好套路的题啊,,, 我们要求的,实际上是一个集合\(n\)个\(1\)中最晚出现的\(1\)的期望时间 显然\(minmax\)容斥 \[E(max\{S\}) = ...

  2. Java考试题之五

    QUESTION 102 Given: 23. Object [] myObjects = { 24. new Integer(12), 25. new String("foo") ...

  3. bug -- android 7.0 popwindow显示位置异常情况解决

    android 7.0 popwindow显示位置异常,在android7.1官方进行解决了,但是还是要多7.0的bug进行解决,我的解决方案里面通过重写popwindow进行适配: import a ...

  4. 解决SurfaceView调用setZOrderOnTop(true)遮挡其他控件

    解决SurfaceView调用setZOrderOnTop(true)遮挡其他控件的问题 http://marller.blog.51cto.com/8699646/1762028 FAQ: Surf ...

  5. bzoj 4332:JSOI2012 分零食

    描述 这里是欢乐的进香河,这里是欢乐的幼儿园. 今天是2月14日,星期二.在这个特殊的日子里,老师带着同学们欢乐地跳着,笑着.校长从幼儿园旁边的小吃店买了大量的零食决定分给同学们.听到这个消息,所有同 ...

  6. 【堆的启发式合并】【P5290】[十二省联考2019]春节十二响

    Description 给定一棵 \(n\) 个节点的树,点有点权,将树的节点划分成多个集合,满足集合的并集是树的点集,最小化每个集合最大点权之和. Limitation \(1~\leq~n~\le ...

  7. HDU 4825 tire树

    Xor Sum Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 132768/132768 K (Java/Others)Total S ...

  8. python蒙特卡洛算法模拟赌博模型

    sklearn实战-乳腺癌细胞数据挖掘 https://study.163.com/course/introduction.htm?courseId=1005269003&utm_campai ...

  9. Hadoop生态圈-phoenix(HBase)的索引配置

    Hadoop生态圈-phoenix(HBase)的索引配置 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 创建索引是为了优化查询,我们可以在phoenix上配置索引方式. 一.修改 ...

  10. 二维数组和指针(C语言)

    二维数组和指针 二维数组和指针1.二维数组和数组元素的地址若有以下定义:int *p, a[3][4]; 1)二维数组a由若干个一维数组组成在C语言中定义的二维数组实际上是一个一维数组,这个一维数组的 ...