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

Go's Declaration Syntax的更多相关文章

  1. 14 Go's Declaration Syntax go语言声明语法

    Go's Declaration Syntax go语言声明语法 7 July 2010 Introduction Newcomers to Go wonder why the declaration ...

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

  3. [转]50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs

    http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/ 50 Shades of Go: Traps, Gotc ...

  4. Delphi XE5教程4:程序和单元概述

    内容源自Delphi XE5 UPDATE 2官方帮助<Delphi Reference>,本人水平有限,欢迎各位高人修正相关错误!也欢迎各位加入到Delphi学习资料汉化中来,有兴趣者可 ...

  5. iOS Developer Libray (中文版)-- Defining Classes 定义类

    该篇是我自己学习iOS开发时阅读文档时随手记下的翻译,有些地方不是很准确,但是意思还是对的,毕竟我英语也不是很好,很多句子无法做到准确的字词翻译,大家可以当做参考,有错误欢迎指出,以后我会尽力翻译的更 ...

  6. C 编译器错误信息中文翻译

    Ambiguous operators need parentheses 不 明确的运算需要用括号括起 Ambiguous symbol ``xxx`` 不明确的符号 Argument list sy ...

  7. MDK常见错误详解集合

    错误代码及错误信息 错误释义 error 1: Out of memory 内存溢出 error 2: Identifier expected 缺标识符 error 3: Unknown identi ...

  8. 使用CSharp编写Google Protobuf插件

    什么是 Google Protocol Buffer? Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 ...

  9. C++标准库第二版笔记 2.1

    C++标准库第二版笔记 2.1 1 Range-Based for 循环 for ( decl : coll ) { statements; } // collaborate 类似C# foreach ...

随机推荐

  1. 循环神经网络(LSTM和GRU)(1)

    循环神经网络的简单实现: import tensorflow as tf x=[1,2] state=[0.0,0.0] w_cell_state=np.array([[0.1,0.2],[0.3,0 ...

  2. SpringBoot 在IDEA中实现热部署(实用版)

    转自https://www.jianshu.com/p/f658fed35786 具体步骤 一.开启IDEA的自动编译(静态) 具体步骤:打开顶部工具栏 File -> Settings -&g ...

  3. 【GMOJ6377】幽曲[埋骨于弘川]

    Description \(n\in[1,500],k\in[2,10]\). Solution 这是一道有点很有难度的题. 先考虑判断一个数是否在数列\(a\)中.由于每次加的数是在\([0,k)\ ...

  4. OC + RAC (九) 过滤

    // 跳跃 : 如下,skip传入2 跳过前面两个值 // 实际用处: 在实际开发中比如 后台返回的数据前面几个没用,我们想跳跃过去,便可以用skip - (void)skip { RACSubjec ...

  5. 路由Vue-router 的使用总结

    1.关于 router-view 匹配 vue 项目使用 vue-router,所有的根级别的路由都是在 App.vue 文件中的 router-view 中渲染的.比如下面的 path: '/' . ...

  6. 【转】django 正则URL 匹配

    django 正则URL 匹配  转自:https://www.cnblogs.com/chenkeven/articles/9305260.html 一.引子 在day17 作业中,我们查看主机详细 ...

  7. php面试专题---8、会话控制考点

    php面试专题---8.会话控制考点 一.总结 一句话总结: 主要是cookie和session的区别,以及用户禁用cookie之后怎么使用session 1.为什么要使用会话控制技术? 因为http ...

  8. PHP7.2中AES加密解密方法mcrypt_module_open()替换方案 Function mcrypt_get_block_size() is deprecated

    直接粘代码,该类是基于微信公众号消息加密解密所提供的PHP DEMO改造而来,目前使用于彬彬大学APP接口token校验中. php的mcrypt 扩展已经过时了大约10年,并且用起来很复杂.因此它被 ...

  9. Log4j log for java(java的日志) 的使用

    log4j的使用,Log4j log for java(java的日志) 是java主流的日志框架,提供各种类型,各种存储,各种格式,多样化的日志服务. 可以再Apache官网下载得到. 我们下载lo ...

  10. win10半夜自动开机的问题分析

    win10半夜自动开机的系统日志: 解决方法一: 1.根据日志判断自动唤醒后,windows更新了时间和代理 服务管理器中,关闭windows update, 但是半夜还会自动开 再关闭服务管理器的w ...