翻译进度
35
分块数量
11
参与人数

Go 官方翻译:Go 常见问题(Go FAQ)

这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。


来源

项目的起源

就在十年前,Go 刚成立的时候,编程世界与今天不同。生产软件通常是用 C++ 或 Java 编写的,GitHub 还不存在,大多数计算机还没有多处理器,除了 Visual Studio 和 Eclipse 之外,几乎没有 IDE 或其他高级工具可用,更别说在互联网上免费使用了。

与此同时,我们对用语言来开发服务软件的复杂程度感到沮丧。自从 C、C++ 和 Java 等语言被开发出来后,计算机的运行速度大大提高了,但编程本身却没有那么发达。而且,尽管多处理器系统已经普遍,对语言的高效性和安全性却几乎没有帮助。

我们决定退一步思考,随着技术的发展,在未来的几年里软件工程将会面临哪些主要问题,以及一种新的语言将如何帮助解决这些问题。例如,多核 CPU 的兴起表明,一种语言应该为某种并发性或并行性提供一流的支持。为了使资源管理在大型并发程序中易于处理,需要垃圾收集,或者至少需要某种安全的、自动的内存管理。

这些考虑引发了一系列的讨论 ,从这些讨论开始,首先作为一套想法和愿望,然后作为一种语言。一个主要的目标是,通过启用工具、自动化一些常见的任务 ( 如代码格式化 ) 和消除在大型代码库中工作的障碍,更多地帮助正在工作的程序员。

有关 Go 的目标以及如何实现这些目标 ( 至少是如何实现这些目标 ) ,更广泛的描述可以在这篇文章中可以找到。

g-sabo 翻译于 1个月前

项目历史

2007 年 9 月 21 日,Robert Griesemer、Rob Pike和Ken Thompson开始在白板上勾画新语言的目标。几天之内,目标就形成了,既一个做某事的计划和一个公平的想法。计划在非全职时间继续进行,同时进行一些其他工作。到 2008 年 1 月,Ken 已经开始研究编译器,用它来探索思想;它用C代码作为输出。到年中,该语言已经成了一个全职项目,并且稳定下来,可以尝试一个产品编译器。2008 年 5 月,Ian Taylor 独立地开始在 GCC 前端使用草案规范。Russ Cox 于 2008 年底加入,帮助将语言和库从原型推向现实。

Go于 2009 年 11 月 10 日成为一个公共开源项目。社区中有无数人提供了想法、讨论和代码。

现在全世界有数百万的Go程序员——gopherg,而且每天都在增加。Go的成功远远超出了我们的预期。

g-sabo 翻译于 1个月前

地鼠吉祥物的起源是什么?

吉祥物和徽标是由 Renée French设计的,他还设计了 Glenda,Plan 9 兔子。一篇关于gopher 的 博客文章 解释了它是如何从几年前她用于 WFMU T 恤设计中衍生出来的。徽标和吉祥物以Creative Commons Attribution 3.0 许可发行。

Gopher 有一份 设计指南 ,说明了他的特征以及如何正确表示它们。

Go 还是 Golang?

语言准确的名称为 Go。Golang 这个名字的出现是因为网站 golang.org,而不是 go.org(我们无法使用此域名)。不过,很多人把 Golang 作为一个表情,例如,该语言的 Twitter 话题标签是 #golang。不管怎样,这门语言的名字只是普通的 Go

另一个注意事项:尽管 官方标志 有两个大写字母,但语言名称是 Go,而不是 GO(注意大小写)。

Summer 翻译于 4周前

你们为什么要创造新的语言?

Go在既有语言与环境下进行系统编程的挫折中诞生。编程变得太难,对语言的选择有一定的责任。 我们必须在高效编译、高效执行或轻松编程之间选择其一,在同样主流的语言中,三者不能同时达到。 程序员们通过转移到PythonJavaScript之类的动态类型语言,而非C++或一定程度上的Java上, 来选择轻松在安全和效率之上。

Go试图成为结合解释型编程的轻松、动态类型语言的高效以及静态类型语言的安全的编译型语言。 它也打算成为现代的,支持网络与多核计算的语言。要满足这些目标,需要解决一些语言上的问题: 一个富有表达能力但轻量级的类型系统,并发与垃圾回收机制,严格的依赖规范等等。 这些无法通过库或工具解决好,必须创造新的语言。

文章 Go 在 Google 中讨论了Go语言设计的其背景和动机,关于本 FAQ 中的许多为题,该文章提供了更多详情。

g-sabo 翻译于 1个月前

Go的前身是什么?

Go主要是C家族的(基本语法),从 Pascal/Modula/Oberon 家族引入了重要的东西(声明,包), 加上一些由 Tony Hoare 的 CSP 所激发的语言的理念,例如 Newsqueak 与 Limbo(并发)。然而, 它是一个全新的语言。在各个方面上,该语言的设计都考虑到程序员做的事情以及如何去编程, 至少是我们进行的那种编程。更实际,也就意味着更有趣。

设计的指导原则是什么?

如今的编程包含了太多记账式的、重复的、文书式的工作。就像 Dick Gabriel 说的那样: “老程序读起来就像健谈的研究工作者与善于学习的书呆子同事之间平和的对话, 而不像同编译器之间的争辩。谁会认为成熟必然带来杂乱?”这样的成熟是值得的—— 没有人想要回到老的语言——但它能更安静地被实现么?

Go试图在两种意义上减少文字的键入次数。贯穿其设计,我们试图减少混乱与复杂性。它没有前置声明与头文件;任何东西都只声明一次。初始化富有表现力,自动且易于使用。 语法的关键字清晰而轻量。啰嗦的表达式(foo.Foo* myFoo = new(foo.Foo)) 可使用 := 声明并初始化结构,通过简单的类型推断来简化。 也许最根本的是,这里没有类型层级:类型就是类型,无需说明它们之间的关系。 这些简化允许Go无需牺牲成熟而富有表现力且易于理解。

另一个重要的原则是保持概念正交。方法可被任何类型实现,结构代表数据而接口代表抽象等等。 正交性使一些东西相结合时发生的事情更易理解。

g-sabo 翻译于 1个月前

使用

Google 是否在内部使用 Go ?

是的。现在有几个Go程序正部署在Google内部产品中。一个例子就是在 golang.org 后台支持的服务。它仅仅是在Google应用引擎上配置的产品中运行的文档服务。

还有个例子就是Google的下载服务器 dl.google.com,它用于释放Chrome二进制文件和其它类似 apt-get 的大型可安装包。

Go并不是谷歌使用的唯一语言,但它是许多领域的关键语言,包括 site reliability engineering (SRE) 和大规模数据处理。

还有哪些公司在用 Go ?

Go在全球范围内的使用量都在增长,尤其是在云计算领域。用Go编写的几个主要的云基础设施项目有DockerKubernetes,还有更多。

它不只是云。Go Wiki有一个页面,定期更新,其中列出了一些使用Go的公司。

Wiki还有一个页面,其中有关于使用该语言的公司和项目的 success stories 的链接。

g-sabo 翻译于 1个月前

Go程序能否与C/C++程序链接?

可以在相同的地址空间中一起使用C和Go,但这并不是很自然的做法,可能需要特殊的接口软件。同样,将C与Go代码链接会放弃Go提供的内存安全性和堆栈管理属性。有时候,绝对有必要使用C库来解决问题,但是这样做总是会引入纯Go代码所没有的风险元素,因此请务必谨慎。

如果确实需要将C与Go一起使用,如何进行取决于Go编译器的实现。Go团队支持了三种Go编译器实现。它们是gc-默认编译器,gccgo-使用GCC后端的gccgo,和gollvm-使用LLVM基础架构的不那么成熟的gollvm。

Gc使用与C不同的调用约定和链接器,因此不能直接从C程序调用,反之亦然。cgo程序提供了“外部函数接口”的机制,以允许在Go代码中安全地调用C库。SWIG将此功能扩展到了C++库。

您也可以将cgo和SWIG与Gccgogollvm一起使用。由于它们使用传统的API,因此将这些编译器的代码直接与GCC/LLVM编译的C或C++程序链接也是可能的,在非常小心的情况下。但是,要安全的执行此操作,需要了解所有相关语言的调用约定,以及从Go调用C或C++时需要注意的堆栈限制。
You can also use cgo and SWIG

Go支持哪些IDE?

Go项目不包括一个自定义的IDE,但是已经设计语言和库来简化源代码分析。结果就是,大多数知名的编辑器和IDE都直接或通过插件良好地支持Go。

具有良好的Go支持的知名IDE和编辑器列表包括Emacs,Vim,VSCode,Atom,Eclipse,Sublime,IntelliJ(通过自定义变体Goland)等。您最喜欢的环境很可能就是您在Go中进行编程的高效环境。

RichardHou 翻译于 2周前

Go 支持谷歌的 Protocol Buffers 吗?

有一个单独的开源项目来提供编译插件和库支持。 在 github.com/golang/protobuf/ 上可以找到。

我能把 Go 的主页翻译成其他语言吗?

当然可以。我们鼓励开发者使用他们的母语重做 Go 语言主页。然而,如果你打算在网站中添加谷歌的 Logo 或商标( golang.org 上面并没有), 你必须遵守 www.google.com/permissions/guidelines.html 上的用户手册。

设计

Go 有运行时吗?

Go 确实有一个扩展库叫做 runtime (运行时),每一个 Go 程序都会使用它。运行时库实现了垃圾回收,并发,栈管理等重要的语言特性。尽管它对于 Go 语言很重要,但是它更类似于 C 语言的 libc 库。

值得注意的是,Go 的运行时不包括 JVM 那样的虚拟机。Go 代码会被预先编译成原生的机器码(某些特别的编译器也可以把它编译为 JavaScript 和 WebAssembly )。因此,尽管「运行时」这个词通常指程序运行的虚拟环境,在 Go 语言中它指的只是一个支持语言重要特性的库。

gonearewe 翻译于 4周前

是否支持 Unicode 标识符?

在设计 Go 时,我们希望确保它不要过于以ASCII为中心,这意味着将标识符空间从7位ASCII的界限中扩展出来。Go 现在的规则——标识符字符必须是由Unicode定义的字母或数字——易于理解和实现,但有一些限制。例如,组合字符被设计排除在外,这也排除了一些语言,如梵文。

这条规则还有一个不幸的后果。由于导出的标识符必须以大写字母开头,根据定义,由某些语言中的字符创建的标识符不能导出。目前唯一的解决办法是使用像X日本語 这样的东西,这显然不能令人满意。

自该语言的最早版本以来,人们就一直在思考如何最好地扩展标识符空间,以适应使用其他母语的程序员。具体该怎么做仍然是一个活跃的讨论话题,未来版本的语言在标识符的定义上可能会更加自由。例如,它可能采纳统一编码(Unicode)组织建议 中关于标识符的一些想法。无论发生什么,都必须兼容地完成,同时保持(或者扩展)字母大小写决定标识符可见性的方式,这仍然是我们最喜欢的Go特性之一。

目前,我们有一个简单的规则,可以在不破坏程序的情况下进行扩展,这个规则可以避免错误,这些错误肯定是由允许不明确标识符的规则引起的。

为什么 Go 没有特性 X?

每种语言都包含新颖的特征,省略了某人最喜欢的特征。Go 的设计着眼于编程的正确性、编译的速度、概念的正交性,以及支持并发性和垃圾收集等特性的需求。您最喜欢的特性可能会丢失,因为它不适合,因为它影响编译速度或设计的清晰度,或者因为它会使基本的系统模型变得太困难。

如果Go缺少功能X,您会感到困扰,请原谅我们,并调查Go的功能。你可能会发现它们以有趣的方式弥补了X的不足。

agantihuman 翻译于 3周前

为什么Go不支持泛型?

我们可能会在未来的某些时候支持泛型。到现在不觉得它们有紧迫感,尽管我们理解一些程序员会觉得急需此功能。

Go是一种编写服务器程序的语言,随着时间的推移这些程序将保持一定的可维护性。

更多背景信息请参阅本文

设计集中于可伸缩性,可读性和并发性。当时,多态编程对于该语言的目标似乎并不重要,因此为简单起见而被省略。

该语言现在变得更成熟了,可以考虑某种形式的通用编程。但是,仍然存在一些警告。

泛型很方便,但是它们会增加类型系统和运行时的复杂性。尽管我们一直在考虑它,但我们尚未找到一种能使价值与复杂性成比例的设计。同时,Go的内置映射和切片,加上使用空接口构造容器的能力(通过显式取消装箱),意味着在许多情况下可以编写代码来实现泛型所能实现的功能(如果不太流畅)。

该主题保持开放。要查看以前为Go设计良好的泛型解决方案的失败尝试,请参阅此建议

zgnMark 翻译于 2周前

为什么 Go 没有异常?

我们认为,将异常耦合到控制结构,就像 try-catch-final 中的那样,会产生复杂的代码。它还倾向于鼓励程序员将太多的普通错误(如未能打开文件)标记为异常。

Go 采用不同的方法。对于普通的错误处理,Go 的多值返回使报告错误变得容易。Go 一种规范的错误类型,与 Go 的其他功能相结合,使错误处理令人愉快,但与其他语言中的错误处理有很大不同。

Go 还具有几个内置函数,用于发出信号并从真正的异常条件中恢复。恢复机制仅作为出错后被拆除的函数状态的一部分来执行,这足以处理报错,但不需要额外的控制结构,并且如果使用得当,可以产生干净的错误处理代码。

有关详细信息,请参阅 Defer, Panic, and Recover 。同样 把错误当成值 描述了一种在 Go 中干净地处理错误的方法,它演示了由于错误只是值,Go 的全部功能都得以依赖在错误处理中。

为什么 Go 没有断言?

它们无可否认是方便的,但我们的经验是程序员将它们用作拐杖,以避免考虑适当的错误处理和报告。正确的错误处理意味着服务器在发生非致命错误后继续运行,而不是崩溃。正确的错误报告意味着直击要害,避免程序员翻阅大量的程序运行错误堆栈。当程序员不熟悉代码时,精确的错误尤其重要。

我们理解这是一个争论点。Go 语言和库中有许多不同于现代实践的东西,只是因为我们觉得有时值得尝试不同的方法。

Summer 翻译于 3周前

为什么在CSP的思想上建立并发性?

并发和多线程编程开发非常困难,现在已经是众所周知的事情。我们认为一部分原因是设计太过复杂,例如pthreads 另一部分是太过强调简单的细节,例如互斥锁,条件变量和内存屏障。 更高级别的接口支持更简单的代码,即使仍然存在互斥体等

Hoare的通信顺序过程(CSP)是为并发提供高级语言支持的最成功模型之一。 Occam和Erlang是源自CSP的两种众所周知的语言。 Go的并发原语来自家族树的不同部分,其主要贡献是将通道作为一流对象的强大概念。几种早期语言的经验表明,CSP模型非常适合过程语言框架。

为什么用 goroutines 代替线程?

Goroutines 是使并发编程变得简单的一部分。这个想法已经存在了一段时间,它是将独立执行的功能(协程)复用到一组线程上当协程被阻止时(例如通过调用阻止系统调用),运行时会自动将同一操作系统线程上的其他协程移动到另一个可运行的线程中,这样它们就不会被阻止。 程序员看不到这些,这就是重点。 结果是,goroutines 的运行变得非常廉价:它们在堆栈内存(只有几千字节)之外的开销很小。

为了缩小堆栈,Go在运行时使用可调整大小的有界堆栈。每次给一个新的 goroutine 几千字节,这就足够了 。否则,运行时会增加(或缩小)用于自动存储堆栈的内存,从而使许多 goroutine 可以驻留在适度的内存中。每个函数调用的 CPU 开销平均约为 3 条简单指令。这使得在同一个地址空间中创建数十万个goroutine 是很实际的。如果 goroutine 只是线程,那么使用少量的 goroutine 就会耗尽系统资源。

snowlyg 翻译于 1周前

为什么未将 映射(map) 操作定义为原子操作?

经过长时间的讨论,人们决定使用 maps 的典型用法不需要从多个 goroutine 安全访问,并且在需要的情况下,map可能是某些已经同步的较大数据结构或计算的一部分。因此,要求所有 map 操作都获取互斥量将减慢大多数程序的速度,并增加少数程序的安全性。但是,这并不是一个容易的决定,因为这意味着不受控制的 map 访问可能会使程序崩溃。

该语言不排除原子 map 更新。在需要时,例如在托管不受信任的程序时,该实现可以互锁 map 访问。

仅在进行更新时,map 访问才是不安全的。只要所有 goroutine 仅读取-查找 map中的元素(包括使用for range 循环对其进行遍历)并且不通过分配元素或进行删除来更改 map,它们就可以安全访问并发 map,无需同步。

为了更正 map 的使用,该语言的某些实现包含特殊检查,当通过并发执行不安全地修改 map 时,该检查会在运行时自动报告。

你接受我的语言更改吗?

人们经常建议对语言进行改进-邮件列表包含此类讨论的丰富历史-但很少接受这些更改。

尽管Go是一个开源项目,但语言和库受compatibility promise 的保护,该更改至少在源代码级别可防止破坏现有程序的更改(有时可能需要重新编译程序以保持最新状态)。如果您的提议违反了 Go 1 规范,则无论其优点如何,我们都无法接受。 Go 的未来主要发行版可能与 Go 1 不兼容,但是关于该主题的讨论才刚刚开始,并且可以肯定的是:在此过程中几乎不会引入这种不兼容性。此外,兼容性承诺鼓励我们为出现这种情况的旧程序提供自动前进的路径。

即使您的建议与Go 1规范兼容,也可能不符合Go的设计目标。文章 转到Google:软件工程服务中的语言设计 解释了Go的起源以及其设计背后的动机。

snowlyg 翻译于 1周前

数据类型

Go 是一种面向对象的编程语言吗?

是也不是。尽管 Go 有类型和方法,并且允许面向对象风格的编程,但没有类型层次结构。Go 中的『接口』概念提供了一种不同的实现方式,我们认为它易于使用,并且在某些方面更通用。还有一些方法可以将类型嵌入到其他类型中,以提供类似于(但不完全相同)子类化的东西。此外,Go 中的方法比 C++ 或 Java 中的方法更通用:它们可以为任何类型的数据定义,甚至可以定义内置类型,如普通的整数。它们不限于 structs (类)。

同时,缺少类型层次结构使 Go 中的『对象』感觉上比 C++ 或 Java 等语言中的『对象』轻很多。

如何获得方法的动态分派?

动态分派方法的唯一方式是通过接口(interface)。结构或其他具体类型上的方法始终是静态的。

Summer 翻译于 4周前

为什么没有类型继承?

面向对象编程,至少在最知名的语言中,涉及了太多关于和类型之间关系的讨论,而这些关系通常可以自动导出。Go 采用了不同的方法去处理。

在Go中,一种类型会自动满足指定其方法子集的任何接口,无需要求程序员提前声明两种类型相关联。除了减少簿记(bookkeeping)之外,这种方法还具有其他真正的优势。类型可以一次满足许多接口,而没有传统的多重继承的复杂性。 接口可以是非常轻量级的-具有一个或甚至零个方法的接口就可以表达一个有用的概念。如果有新想法出现或要进行测试,则可以在事之后添加接口,而无需注释原始类型。因为类型和接口之间没有明确的关系,所以没有类型层次结构可以管理或讨论。

I可以使用这些想法来构造类似于类型安全的Unix管道的东西。例如, fmt.Fprintf 为什么可以格式化任何类型输出,不仅仅是文件,或者  bufio包如何和文件 I/O 完全分离,或者 image 包如何生成压缩的图像文件。 所有这些思路都有基于一个接口 (io.Writer) 代表 一个方法(Write)。而这仅仅是表面。 Go 的接口对程序的结构产生了深远的影响。

这需要一些时间来习惯,但是这种隐式类型依赖类型是Go最具生产力的事情之一。

为什么len是函数而不是方法?

我们讨论了这个问题,但是决定以函数实现 len ,因为函数在实践中更友好,并且没有使关于基本类型的接口(从Go类型意义上来说)的问题复杂化。

为什么Go不支持方法和运算符的重载?

如果方法调度不需要进行类型匹配,则简化该方法调度。其他语言的经验告诉我们,拥有同名但不同签名的各种方法有时很有用,但在实践中也可能令人困惑和脆弱。在 Go 的类型系统中,仅按名称匹配和需要类型一致性是一项重大简化决策。

关于操作过载,它似乎比绝对要求更为方便。同样,没有它,事情就简单多了。

为什么Go没有“实现”声明?

Go类型通过实现该接口的方法来满足该接口,仅此而已。此属性允许定义和使用接口,而无需修改现有代码。它启用了一种结构化类型,该结构促进了关注点的分离并改善了代码的重用性,并使得在代码开发时出现的模式上更容易构建。接口的语义是Go敏捷,轻便的感觉的主要原因之一。

有关更多详细信息,请参见关于类型继承的问题

snowlyg 翻译于 1周前

如何才能确保自定义类型实现了接口?

你可以尝试像下面这样用 T 的零值或 T 的指针来进行赋值,编译器将为你检查类型 T 是否实现了 接口I

type T struct{}
var _ I = T{}       // 检查 T 是否实现了接口 I
var _ I = (*T)(nil) // 检查 *T 是否实现了接口 I

如果T 或者 *T并没有实现接口I,编译时会报错。

如果你希望使用接口的人明确声明他们实现了接口,你可以在接口的方法列表里添加一个名字具有描述性的方法,像这样:

type Fooer interface {
    Foo()
    ImplementsFooer()
}

一个类型只有实现了 ImplementsFooer方法才能实现 Fooer接口, 这样在 go doc 生成的文档中会明确地记录下这点。

type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}

一般情况下不需要这样写,因为这限制了接口的灵活使用。然而,在设计一批相似接口时,这样写可以有效消除歧义。

gonearewe 翻译于 4周前

为什么类型 T 不满足 Eual 接口?

思考这个表示可以同另一个值比较的对象的简单接口:

type Equaler interface {
    Equal(Equaler) bool
}

和这种类型, T:

type T int
func (t T) Equal(u T) bool { return t == u } // 不满足 Equal 

与某些多态类型系统中的类似情况不同, T 不实现 Equaler。 T.Equal 的参数类型是 T,而不是字面意义上必需的 Equaler 类型

在 Go 中,类型系统不会提升 Equal 的参数;这是程序员的责任,如 T2 类型所示,它确实实现了 Equaler

type T2 int
func (t T2) Equal(u Equaler) bool { return t == u.(T2) }  // 满足 Equaler

不过,即使这与其他类型系统都不一样, 因为在 Go any  类型中,可以将满足 Equaler 的类型作为 T2.Equal的参数传递,并且在运行时我们必须检查该参数是否为类型 T2 。一些语言安排在编译时做出保证。

一个相关的例子是相反的:

type Opener interface {
   Open() Reader
}

func (t T3) Open() *os.File

I在Go中,T3 不满足 Opener,尽管它可能是另一种语言。

在这种情况下,Go的类型系统确实对程序员没有多大作用,但是缺少子类型化使得关于接口满意度的规则非常容易陈述:函数的名称和签名是否与接口的名称和签名完全相同?Go的规则也易于有效实施。 我们认为这些好处弥补了自动类型提升的不足。如果 Go 有一天可以采用某种形式的多态类型输入,我们希望将有一种表达这些示例思想的方法,并且可以对它们进行静态检查。

snowlyg 翻译于 1周前

我是否可以将 []T 转换为 []interface{}?

无法直接转换, 语言规范不允许这样做,因为这两种类型在内存中的表示方式不同。如果有必要可以将元素分别复制到目标切片。这个示例将 int 的一部分转换为 interface{} 的一部分:

t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
    s[i] = v
}

我是否可以将 []T1 转换为 []T2 如果T1 和 T2 有相同的基础类型?

此代码示例的最后一行不会被编译。

type T1 int
type T2 int
var t1 T1
var x = T2(t1) // OK
var st1 []T1
var sx = ([]T2)(st1) // NOT OK

在Go中,类型与方法紧密相关,因为每个命名类型都有一个(可能为空)方法集。 T通用规则是 你可以改变要转换类型的名称 (这可能改变它的方法集) ,单改变复核类型元素的名称 (和方法集) 。 Go 需要你明确说明类型转换。

snowlyg 翻译于 1周前

为什么 nil 错误值不等于 nil?

在底层, interface是由两种元素组成, 类型T和值V. V是一个像int, struct或者指针类型的具体值,不是接口类型本身,并且有一个类型T. 例如, 如果我们赋值一个int类型的值 3 到一个 interface, 那么这个 interface 值相应地有T=int V3组成. 值V是在 interface 中是动态的, 在程序执行过程中,一个指定的 interface 类型可能有不同值V和相应的类型T
一个接口类型的值为nil当且仅当值VT都未设置的时候,(T=nil, V没有设置), 典型的, 一个nil接口对应的类型也总是nil, 如果我们赋值一个*int类型的空指针给 一个interface,那么这个接口 interface 内部的类型T就是*int, 不论他这个指针本身是否为 nil: (T=*int, V=nil). 这样一个接口类型就不为 nil, 即使V部分是一个nil指针.

这种情况可能令人困惑, 当一个nil值存储赋值给一个接口类型作为返回值例如error

func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = ErrBad
    }
    return p // 总是返回一个非空类型的错误
}

如果运行良好, 函数返回一个空指针, 因此返回值是一个error 接口值, 内部类型是(T=*MyError, V=nil). 这意味着如果调用者讲返回值与nil比较, 结果总是有错误, 即(returnsError() != nil 总是成立的). 要正确的返回一个nil 错误给调用者, 函数必须返回一个明确的nil:

func returnsError() error {
    if bad() {
        return ErrBad
    }
    return nil
}

对于总是返回错误的函数,最好直接在签名中使用error类型而不是一个例如*MyError具体的错误类型, 以保证返回我们希望的错误, 例如标准库中的os.Open函数,返回错误类型,如果不是nil, 总是返回一个具体的类型os.PathError

无论何时使用接口类型的时候,类似的情况都会出现. 谨记, 如果具体类型赋值给几口, 接口就不为nil, 更多详细信息,参考 反射规则.

endless-horizon 翻译于 1周前

为什么没有像 C 中那样的无标记的联合??

未标记的联合将违反 Go 的内存安全保证。

为什么 Go 没有变体类型??

变体类型,也称为代数类型,提供一种方法来指定值可以采用一组其他类型中的一种,但只能是那些类型。系统编程中的一个常见示例将指定错误为网络错误,安全错误或应用程序错误,并允许调用者通过检查错误的类型来区分问题的根源。另一个示例是语法树,其中每个节点可以是不同的类型:声明,语句,赋值等等。

我们考虑过将变体类型添加到 Go 中,但是经过讨论后,他们决定将它们排除在外,因为它们与接口的混淆方式相互重叠。如果变量类型的元素本身是接口,将会发生什么?

同样,该语言已经涵盖了某些变体类型所针对的内容。使用接口值来保存错误并使用类型开关来区分大小写,很容易表达错误示例。语法树示例也是可行的,尽管不那么优雅。

为什么 Go 没有协变结果类型?

协变结果类型将意味着类似下面的这个接口

type Copyable interface {
    Copy() interface{}
}

你对这个方法满意吗

func (v Value) Copy() Value

因为 Value 实现了空接口。在 Go 中,方法类型必须完全匹配,因此 Value 不会实现 Copyable。Go 将类型的作用(方法)与类型的实现分开。如果两个方法返回不同的类型,则它们不会做相同的事情。想要协变结果类型的程序员通常试图通过接口表达类型层次。在 Go 中,接口与实现之间的清晰区分是很自然的。

SmauelL 翻译于 1周前

Values

Why does Go not provide implicit numeric conversions?

The convenience of automatic conversion between numeric types in C is outweighed by the confusion it causes. When is an expression unsigned? How big is the value? Does it overflow? Is the result portable, independent of the machine on which it executes? It also complicates the compiler; “the usual arithmetic conversions” are not easy to implement and inconsistent across architectures. For reasons of portability, we decided to make things clear and straightforward at the cost of some explicit conversions in the code. The definition of constants in Go—arbitrary precision values free of signedness and size annotations—ameliorates matters considerably, though.

A related detail is that, unlike in C, int and int64 are distinct types even if int is a 64-bit type. The int type is generic; if you care about how many bits an integer holds, Go encourages you to be explicit.

How do constants work in Go?

Although Go is strict about conversion between variables of different numeric types, constants in the language are much more flexible. Literal constants such as 233.14159 and math.Pi occupy a sort of ideal number space, with arbitrary precision and no overflow or underflow. For instance, the value of math.Pi is specified to 63 places in the source code, and constant expressions involving the value keep precision beyond what a float64 could hold. Only when the constant or constant expression is assigned to a variable—a memory location in the program—does it become a "computer" number with the usual floating-point properties and precision.

Also, because they are just numbers, not typed values, constants in Go can be used more freely than variables, thereby softening some of the awkwardness around the strict conversion rules. One can write expressions such as

sqrt2 := math.Sqrt(2)

without complaint from the compiler because the ideal number 2 can be converted safely and accurately to a float64 for the call to math.Sqrt.

A blog post titled Constants explores this topic in more detail.

Why are maps built in?

The same reason strings are: they are such a powerful and important data structure that providing one excellent implementation with syntactic support makes programming more pleasant. We believe that Go's implementation of maps is strong enough that it will serve for the vast majority of uses. If a specific application can benefit from a custom implementation, it's possible to write one but it will not be as convenient syntactically; this seems a reasonable tradeoff.

Why don't maps allow slices as keys?

Map lookup requires an equality operator, which slices do not implement. They don't implement equality because equality is not well defined on such types; there are multiple considerations involving shallow vs. deep comparison, pointer vs. value comparison, how to deal with recursive types, and so on. We may revisit this issue—and implementing equality for slices will not invalidate any existing programs—but without a clear idea of what equality of slices should mean, it was simpler to leave it out for now.

In Go 1, unlike prior releases, equality is defined for structs and arrays, so such types can be used as map keys. Slices still do not have a definition of equality, though.

Why are maps, slices, and channels references while arrays are values?

There's a lot of history on that topic. Early on, maps and channels were syntactically pointers and it was impossible to declare or use a non-pointer instance. Also, we struggled with how arrays should work. Eventually we decided that the strict separation of pointers and values made the language harder to use. Changing these types to act as references to the associated, shared data structures resolved these issues. This change added some regrettable complexity to the language but had a large effect on usability: Go became a more productive, comfortable language when it was introduced.

Writing Code

How are libraries documented?

There is a program, godoc, written in Go, that extracts package documentation from the source code and serves it as a web page with links to declarations, files, and so on. An instance is running at golang.org/pkg/. In fact, godoc implements the full site at golang.org/.

godoc instance may be configured to provide rich, interactive static analyses of symbols in the programs it displays; details are listed here.

For access to documentation from the command line, the go tool has a doc subcommand that provides a textual interface to the same information.

是否有Go 编程风格指南?

没有明确的风格指南, 但是有一些公认的 "Go 风格"

Go 在命名,组织结构,文件组织目录方面有一些约定, Effective Go给出了一些此类的建议.更直接的, gofmt是一个旨在强制性遵循这些规则的代码格式工具;它取代了通常意义上的"做与不做"的风格; Go 仓库中的所有代码和在开源界的绝大部分代码, 都已经通过gofmt格式化.

标题为Go 代码审核评论的文档, 是一些开发者容易忽略的关于 Go风格细节的文章的合集. 它是Go 项目审核人员的实用参考手册.

如何向 Go 库提交补丁?

库源码位于 项目的src 目录中, 如果您要进行重大更改, 请在行动之前先邮件进行沟通讨论.

有关如何进行的更多信息,请参阅文档为Go项目贡献

endless-horizon 翻译于 1周前

go get 克隆仓库时为什么要用 HTTPS?

很多公司通常对出站流量仅开放标准 TCP 端口 80(HTTP)和 443(HTTPS),禁止出站流量访问包括 TCP 端口 9418(git)和 22(SSH)在内的其他端口。git 使用 HTTPS 代替 HTTP,增强了默认的鉴权机制,可以防止中间人攻击,窃听和干预攻击。因此 git get 命令为了安全使用 HTTPS。
你可以配置 Git 用 HTTPS 认证还是 SSH 认证。你可以在 $HOME/.netrc 文件中添加一行,来使用 HTTPS 认证,git 会读取这些参数:

machine github.com login USERNAME password APIKEY

对于 GitHub 账户,密码可以是 个人 access token
你可以配置 Git 对于匹配某些前缀的 URL 用 SSH 来代替 HTTPS。例如,如果你想要对所有的 GitHub 的访问请求都使用 SSH,把下面这些内容添加到你的 ~/.gitconfig

[url "ssh://git@github.com/"]
    insteadOf = https://github.com/
lxbwolf 翻译于 1个月前

How should I manage package versions using "go get"?

Since the inception of the project, Go has had no explicit concept of package versions, but that is changing. Versioning is a source of significant complexity, especially in large code bases, and it has taken some time to develop an approach that works well at scale in a large enough variety of situations to be appropriate to supply to all Go users.

The Go 1.11 release adds new, experimental support for package versioning to the go command, in the form of Go modules. For more information, see the Go 1.11 release notes and the go command documentation.

Regardless of the actual package management technology, "go get" and the larger Go toolchain does provide isolation of packages with different import paths. For example, the standard library's html/template and text/template coexist even though both are "package template". This observation leads to some advice for package authors and package users.

Packages intended for public use should try to maintain backwards compatibility as they evolve. The Go 1 compatibility guidelines are a good reference here: don't remove exported names, encourage tagged composite literals, and so on. If different functionality is required, add a new name instead of changing an old one. If a complete break is required, create a new package with a new import path.

If you're using an externally supplied package and worry that it might change in unexpected ways, but are not yet using Go modules, the simplest solution is to copy it to your local repository. This is the approach Google takes internally and is supported by the go command through a technique called "vendoring". This involves storing a copy of the dependency under a new import path that identifies it as a local copy. See the design document for details.

Pointers and Allocation

When are function parameters passed by value?

As in all languages in the C family, everything in Go is passed by value. That is, a function always gets a copy of the thing being passed, as if there were an assignment statement assigning the value to the parameter. For instance, passing an int value to a function makes a copy of the int, and passing a pointer value makes a copy of the pointer, but not the data it points to. (See a later section for a discussion of how this affects method receivers.)

Map and slice values behave like pointers: they are descriptors that contain pointers to the underlying map or slice data. Copying a map or slice value doesn't copy the data it points to. Copying an interface value makes a copy of the thing stored in the interface value. If the interface value holds a struct, copying the interface value makes a copy of the struct. If the interface value holds a pointer, copying the interface value makes a copy of the pointer, but again not the data it points to.

Note that this discussion is about the semantics of the operations. Actual implementations may apply optimizations to avoid copying as long as the optimizations do not change the semantics.

When should I use a pointer to an interface?

Almost never. Pointers to interface values arise only in rare, tricky situations involving disguising an interface value's type for delayed evaluation.

It is a common mistake to pass a pointer to an interface value to a function expecting an interface. The compiler will complain about this error but the situation can still be confusing, because sometimes a pointer is necessary to satisfy an interface. The insight is that although a pointer to a concrete type can satisfy an interface, with one exception a pointer to an interface can never satisfy an interface.

Consider the variable declaration,

var w io.Writer

The printing function fmt.Fprintf takes as its first argument a value that satisfies io.Writer—something that implements the canonical Write method. Thus we can write

fmt.Fprintf(w, "hello, world.")

If however we pass the address of w, the program will not compile.

fmt.Fprintf(&w, "hello, world.") // Compile-time error.

The one exception is that any value, even a pointer to an interface, can be assigned to a variable of empty interface type (interface{}). Even so, it's almost certainly a mistake if the value is a pointer to an interface; the result can be confusing.

Should I define methods on values or pointers?

func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct)  valueMethod()   { } // method on value

For programmers unaccustomed to pointers, the distinction between these two examples can be confusing, but the situation is actually very simple. When defining a method on a type, the receiver (s in the above examples) behaves exactly as if it were an argument to the method. Whether to define the receiver as a value or as a pointer is the same question, then, as whether a function argument should be a value or a pointer. There are several considerations.

First, and most important, does the method need to modify the receiver? If it does, the receiver must be a pointer. (Slices and maps act as references, so their story is a little more subtle, but for instance to change the length of a slice in a method the receiver must still be a pointer.) In the examples above, if pointerMethod modifies the fields of s, the caller will see those changes, but valueMethod is called with a copy of the caller's argument (that's the definition of passing a value), so changes it makes will be invisible to the caller.

By the way, in Java method receivers are always pointers, although their pointer nature is somewhat disguised (and there is a proposal to add value receivers to the language). It is the value receivers in Go that are unusual.

Second is the consideration of efficiency. If the receiver is large, a big struct for instance, it will be much cheaper to use a pointer receiver.

Next is consistency. If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used. See the section on method sets for details.

For types such as basic types, slices, and small structs, a value receiver is very cheap so unless the semantics of the method requires a pointer, a value receiver is efficient and clear.

What's the difference between new and make?

In short: new allocates memory, while make initializes the slice, map, and channel types.

See the relevant section of Effective Go for more details.

What is the size of an int on a 64 bit machine?

The sizes of int and uint are implementation-specific but the same as each other on a given platform. For portability, code that relies on a particular size of value should use an explicitly sized type, like int64. On 32-bit machines the compilers use 32-bit integers by default, while on 64-bit machines integers have 64 bits. (Historically, this was not always true.)

On the other hand, floating-point scalars and complex types are always sized (there are no float or complex basic types), because programmers should be aware of precision when using floating-point numbers. The default type used for an (untyped) floating-point constant is float64. Thus foo := 3.0 declares a variable foo of type float64. For a float32 variable initialized by an (untyped) constant, the variable type must be specified explicitly in the variable declaration:

var foo float32 = 3.0

Alternatively, the constant must be given a type with a conversion as in foo := float32(3.0).

How do I know whether a variable is allocated on the heap or the stack?

From a correctness standpoint, you don't need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.

The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.

In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.

Why does my Go process use so much virtual memory?

The Go memory allocator reserves a large region of virtual memory as an arena for allocations. This virtual memory is local to the specific Go process; the reservation does not deprive other processes of memory.

To find the amount of actual memory allocated to a Go process, use the Unix top command and consult the RES (Linux) or RSIZE (macOS) columns.

Concurrency

What operations are atomic? What about mutexes?

A description of the atomicity of operations in Go can be found in the Go Memory Model document.

Low-level synchronization and atomic primitives are available in the sync and sync/atomic packages. These packages are good for simple tasks such as incrementing reference counts or guaranteeing small-scale mutual exclusion.

For higher-level operations, such as coordination among concurrent servers, higher-level techniques can lead to nicer programs, and Go supports this approach through its goroutines and channels. For instance, you can structure your program so that only one goroutine at a time is ever responsible for a particular piece of data. That approach is summarized by the original Go proverb,

Do not communicate by sharing memory. Instead, share memory by communicating.

See the Share Memory By Communicating code walk and its associated article for a detailed discussion of this concept.

Large concurrent programs are likely to borrow from both these toolkits.

Why doesn't my program run faster with more CPUs?

Whether a program runs faster with more CPUs depends on the problem it is solving. The Go language provides concurrency primitives, such as goroutines and channels, but concurrency only enables parallelism when the underlying problem is intrinsically parallel. Problems that are intrinsically sequential cannot be sped up by adding more CPUs, while those that can be broken into pieces that can execute in parallel can be sped up, sometimes dramatically.

Sometimes adding more CPUs can slow a program down. In practical terms, programs that spend more time synchronizing or communicating than doing useful computation may experience performance degradation when using multiple OS threads. This is because passing data between threads involves switching contexts, which has significant cost, and that cost can increase with more CPUs. For instance, the prime sieve example from the Go specification has no significant parallelism although it launches many goroutines; increasing the number of threads (CPUs) is more likely to slow it down than to speed it up.

For more detail on this topic see the talk entitled Concurrency is not Parallelism.

How can I control the number of CPUs?

The number of CPUs available simultaneously to executing goroutines is controlled by the GOMAXPROCS shell environment variable, whose default value is the number of CPU cores available. Programs with the potential for parallel execution should therefore achieve it by default on a multiple-CPU machine. To change the number of parallel CPUs to use, set the environment variable or use the similarly-named function of the runtime package to configure the run-time support to utilize a different number of threads. Setting it to 1 eliminates the possibility of true parallelism, forcing independent goroutines to take turns executing.

The runtime can allocate more threads than the value of GOMAXPROCS to service multiple outstanding I/O requests. GOMAXPROCS only affects how many goroutines can actually execute at once; arbitrarily more may be blocked in system calls.

Go's goroutine scheduler is not as good as it needs to be, although it has improved over time. In the future, it may better optimize its use of OS threads. For now, if there are performance issues, setting GOMAXPROCS on a per-application basis may help.

Why is there no goroutine ID?

Goroutines do not have names; they are just anonymous workers. They expose no unique identifier, name, or data structure to the programmer. Some people are surprised by this, expecting the go statement to return some item that can be used to access and control the goroutine later.

The fundamental reason goroutines are anonymous is so that the full Go language is available when programming concurrent code. By contrast, the usage patterns that develop when threads and goroutines are named can restrict what a library using them can do.

Here is an illustration of the difficulties. Once one names a goroutine and constructs a model around it, it becomes special, and one is tempted to associate all computation with that goroutine, ignoring the possibility of using multiple, possibly shared goroutines for the processing. If the net/http package associated per-request state with a goroutine, clients would be unable to use more goroutines when serving a request.

Moreover, experience with libraries such as those for graphics systems that require all processing to occur on the "main thread" has shown how awkward and limiting the approach can be when deployed in a concurrent language. The very existence of a special thread or goroutine forces the programmer to distort the program to avoid crashes and other problems caused by inadvertently operating on the wrong thread.

For those cases where a particular goroutine is truly special, the language provides features such as channels that can be used in flexible ways to interact with it.

Functions and Methods

Why do T and *T have different method sets?

As the Go specification says, the method set of a type T consists of all methods with receiver type T, while that of the corresponding pointer type *T consists of all methods with receiver *T or T. That means the method set of *T includes that of T, but not the reverse.

This distinction arises because if an interface value contains a pointer *T, a method call can obtain a value by dereferencing the pointer, but if an interface value contains a value T, there is no safe way for a method call to obtain a pointer. (Doing so would allow a method to modify the contents of the value inside the interface, which is not permitted by the language specification.)

Even in cases where the compiler could take the address of a value to pass to the method, if the method modifies the value the changes will be lost in the caller. As an example, if the Write method of bytes.Buffer used a value receiver rather than a pointer, this code:

var buf bytes.Buffer
io.Copy(buf, os.Stdin)

would copy standard input into a copy of buf, not into buf itself. This is almost never the desired behavior.

What happens with closures running as goroutines?

Some confusion may arise when using closures with concurrency. Consider the following program:

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}

One might mistakenly expect to see a, b, c as the output. What you'll probably see instead is c, c, c. This is because each iteration of the loop uses the same instance of the variable v, so each closure shares that single variable. When the closure runs, it prints the value of v at the time fmt.Println is executed, but v may have been modified since the goroutine was launched. To help detect this and other problems before they happen, run go vet.

To bind the current value of v to each closure as it is launched, one must modify the inner loop to create a new variable each iteration. One way is to pass the variable as an argument to the closure:

    for _, v := range values {
        go func(u string) {
            fmt.Println(u)
            done <- true
        }(v)
    }

In this example, the value of v is passed as an argument to the anonymous function. That value is then accessible inside the function as the variable u.

Even easier is just to create a new variable, using a declaration style that may seem odd but works fine in Go:

    for _, v := range values {
        v := v // create a new 'v'.
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

This behavior of the language, not defining a new variable for each iteration, may have been a mistake in retrospect. It may be addressed in a later version but, for compatibility, cannot change in Go version 1.

Control flow

Why does Go not have the ?: operator?

There is no ternary testing operation in Go. You may use the following to achieve the same result:

if expr {
    n = trueVal
} else {
    n = falseVal
}

The reason ?: is absent from Go is that the language's designers had seen the operation used too often to create impenetrably complex expressions. The if-else form, although longer, is unquestionably clearer. A language needs only one conditional control flow construct.

Packages and Testing

How do I create a multifile package?

Put all the source files for the package in a directory by themselves. Source files can refer to items from different files at will; there is no need for forward declarations or a header file.

Other than being split into multiple files, the package will compile and test just like a single-file package.

How do I write a unit test?

Create a new file ending in _test.go in the same directory as your package sources. Inside that file, import "testing" and write functions of the form

func TestFoo(t *testing.T) {
    ...
}

Run go test in that directory. That script finds the Test functions, builds a test binary, and runs it.

See the How to Write Go Code document, the testing package and the go test subcommand for more details.

Where is my favorite helper function for testing?

Go's standard testing package makes it easy to write unit tests, but it lacks features provided in other language's testing frameworks such as assertion functions. An earlier section of this document explained why Go doesn't have assertions, and the same arguments apply to the use of assert in tests. Proper error handling means letting other tests run after one has failed, so that the person debugging the failure gets a complete picture of what is wrong. It is more useful for a test to report that isPrime gives the wrong answer for 2, 3, 5, and 7 (or for 2, 4, 8, and 16) than to report that isPrime gives the wrong answer for 2 and therefore no more tests were run. The programmer who triggers the test failure may not be familiar with the code that fails. Time invested writing a good error message now pays off later when the test breaks.

A related point is that testing frameworks tend to develop into mini-languages of their own, with conditionals and controls and printing mechanisms, but Go already has all those capabilities; why recreate them? We'd rather write tests in Go; it's one fewer language to learn and the approach keeps the tests straightforward and easy to understand.

If the amount of extra code required to write good errors seems repetitive and overwhelming, the test might work better if table-driven, iterating over a list of inputs and outputs defined in a data structure (Go has excellent support for data structure literals). The work to write a good test and good error messages will then be amortized over many test cases. The standard Go library is full of illustrative examples, such as in the formatting tests for the fmt package.

Why isn't X in the standard library?

The standard library's purpose is to support the runtime, connect to the operating system, and provide key functionality that many Go programs require, such as formatted I/O and networking. It also contains elements important for web programming, including cryptography and support for standards like HTTP, JSON, and XML.

There is no clear criterion that defines what is included because for a long time, this was the only Go library. There are criteria that define what gets added today, however.

New additions to the standard library are rare and the bar for inclusion is high. Code included in the standard library bears a large ongoing maintenance cost (often borne by those other than the original author), is subject to the Go 1 compatibility promise (blocking fixes to any flaws in the API), and is subject to the Go release schedule, preventing bug fixes from being available to users quickly.

Most new code should live outside of the standard library and be accessible via the go tool's go get command. Such code can have its own maintainers, release cycle, and compatibility guarantees. Users can find packages and read their documentation at godoc.org.

Although there are pieces in the standard library that don't really belong, such as log/syslog, we continue to maintain everything in the library because of the Go 1 compatibility promise. But we encourage most new code to live elsewhere.

Implementation

What compiler technology is used to build the compilers?

There are several production compilers for Go, and a number of others in development for various platforms.

The default compiler, gc, is included with the Go distribution as part of the support for the go command. Gc was originally written in C because of the difficulties of bootstrapping—you'd need a Go compiler to set up a Go environment. But things have advanced and since the Go 1.5 release the compiler has been a Go program. The compiler was converted from C to Go using automatic translation tools, as described in this design document and talk. Thus the compiler is now "self-hosting", which means we needed to face the bootstrapping problem. The solution is to have a working Go installation already in place, just as one normally has with a working C installation. The story of how to bring up a new Go environment from source is described here and here.

Gc is written in Go with a recursive descent parser and uses a custom loader, also written in Go but based on the Plan 9 loader, to generate ELF/Mach-O/PE binaries.

At the beginning of the project we considered using LLVM for gc but decided it was too large and slow to meet our performance goals. More important in retrospect, starting with LLVM would have made it harder to introduce some of the ABI and related changes, such as stack management, that Go requires but are not part of the standard C setup. A new LLVM implementation is starting to come together now, however.

The Gccgo compiler is a front end written in C++ with a recursive descent parser coupled to the standard GCC back end.

Go turned out to be a fine language in which to implement a Go compiler, although that was not its original goal. Not being self-hosting from the beginning allowed Go's design to concentrate on its original use case, which was networked servers. Had we decided Go should compile itself early on, we might have ended up with a language targeted more for compiler construction, which is a worthy goal but not the one we had initially.

Although gc does not use them (yet?), a native lexer and parser are available in the go package and there is also a native type checker.

How is the run-time support implemented?

Again due to bootstrapping issues, the run-time code was originally written mostly in C (with a tiny bit of assembler) but it has since been translated to Go (except for some assembler bits). Gccgo's run-time support uses glibc. The gccgo compiler implements goroutines using a technique called segmented stacks, supported by recent modifications to the gold linker. Gollvm similarly is built on the corresponding LLVM infrastructure.

Why is my trivial program such a large binary?

The linker in the gc toolchain creates statically-linked binaries by default. All Go binaries therefore include the Go runtime, along with the run-time type information necessary to support dynamic type checks, reflection, and even panic-time stack traces.

A simple C "hello, world" program compiled and linked statically using gcc on Linux is around 750 kB, including an implementation of printf. An equivalent Go program using fmt.Printf weighs a couple of megabytes, but that includes more powerful run-time support and type and debugging information.

A Go program compiled with gc can be linked with the -ldflags=-w flag to disable DWARF generation, removing debugging information from the binary but with no other loss of functionality. This can reduce the binary size substantially.

Can I stop these complaints about my unused variable/import?

The presence of an unused variable may indicate a bug, while unused imports just slow down compilation, an effect that can become substantial as a program accumulates code and programmers over time. For these reasons, Go refuses to compile programs with unused variables or imports, trading short-term convenience for long-term build speed and program clarity.

Still, when developing code, it's common to create these situations temporarily and it can be annoying to have to edit them out before the program will compile.

Some have asked for a compiler option to turn those checks off or at least reduce them to warnings. Such an option has not been added, though, because compiler options should not affect the semantics of the language and because the Go compiler does not report warnings, only errors that prevent compilation.

There are two reasons for having no warnings. First, if it's worth complaining about, it's worth fixing in the code. (And if it's not worth fixing, it's not worth mentioning.) Second, having the compiler generate warnings encourages the implementation to warn about weak cases that can make compilation noisy, masking real errors that should be fixed.

It's easy to address the situation, though. Use the blank identifier to let unused things persist while you're developing.

import "unused"

// This declaration marks the import as used by referencing an
// item from the package.
var _ = unused.Item  // TODO: Delete before committing!

func main() {
    debugData := debug.Profile()
    _ = debugData // Used only during debugging.
    ....
}

Nowadays, most Go programmers use a tool, goimports, which automatically rewrites a Go source file to have the correct imports, eliminating the unused imports issue in practice. This program is easily connected to most editors to run automatically when a Go source file is written.

Why does my virus-scanning software think my Go distribution or compiled binary is infected?

This is a common occurrence, especially on Windows machines, and is almost always a false positive. Commercial virus scanning programs are often confused by the structure of Go binaries, which they don't see as often as those compiled from other languages.

If you've just installed the Go distribution and the system reports it is infected, that's certainly a mistake. To be really thorough, you can verify the download by comparing the checksum with those on the downloads page.

In any case, if you believe the report is in error, please report a bug to the supplier of your virus scanner. Maybe in time virus scanners can learn to understand Go programs.

Performance

Why does Go perform badly on benchmark X?

One of Go's design goals is to approach the performance of C for comparable programs, yet on some benchmarks it does quite poorly, including several in golang.org/x/exp/shootout. The slowest depend on libraries for which versions of comparable performance are not available in Go. For instance, pidigits.go depends on a multi-precision math package, and the C versions, unlike Go's, use GMP (which is written in optimized assembler). Benchmarks that depend on regular expressions (regex-dna.go, for instance) are essentially comparing Go's native regexp package to mature, highly optimized regular expression libraries like PCRE.

Benchmark games are won by extensive tuning and the Go versions of most of the benchmarks need attention. If you measure comparable C and Go programs (reverse-complement.go is one example), you'll see the two languages are much closer in raw performance than this suite would indicate.

Still, there is room for improvement. The compilers are good but could be better, many libraries need major performance work, and the garbage collector isn't fast enough yet. (Even if it were, taking care not to generate unnecessary garbage can have a huge effect.)

In any case, Go can often be very competitive. There has been significant improvement in the performance of many programs as the language and tools have developed. See the blog post about profiling Go programs for an informative example.

Changes from C

Why is the syntax so different from C?

Other than declaration syntax, the differences are not major and stem from two desires. First, the syntax should feel light, without too many mandatory keywords, repetition, or arcana. Second, the language has been designed to be easy to analyze and can be parsed without a symbol table. This makes it much easier to build tools such as debuggers, dependency analyzers, automated documentation extractors, IDE plug-ins, and so on. C and its descendants are notoriously difficult in this regard.

Why are declarations backwards?

They're only backwards if you're used to C. In C, the notion is that a variable is declared like an expression denoting its type, which is a nice idea, but the type and expression grammars don't mix very well and the results can be confusing; consider function pointers. Go mostly separates expression and type syntax and that simplifies things (using prefix * for pointers is an exception that proves the rule). In C, the declaration

    int* a, b;

declares a to be a pointer but not b; in Go

    var a, b *int

declares both to be pointers. This is clearer and more regular. Also, the := short declaration form argues that a full variable declaration should present the same order as := so

    var a uint64 = 1

has the same effect as

    a := uint64(1)

Parsing is also simplified by having a distinct grammar for types that is not just the expression grammar; keywords such as func and chan keep things clear.

See the article about Go's Declaration Syntax for more details.

Why is there no pointer arithmetic?

Safety. Without pointer arithmetic it's possible to create a language that can never derive an illegal address that succeeds incorrectly. Compiler and hardware technology have advanced to the point where a loop using array indices can be as efficient as a loop using pointer arithmetic. Also, the lack of pointer arithmetic can simplify the implementation of the garbage collector.

Why are ++ and -- statements and not expressions? And why postfix, not prefix?

Without pointer arithmetic, the convenience value of pre- and postfix increment operators drops. By removing them from the expression hierarchy altogether, expression syntax is simplified and the messy issues around order of evaluation of ++ and -- (consider f(i++) and p[i] = q[++i]) are eliminated as well. The simplification is significant. As for postfix vs. prefix, either would work fine but the postfix version is more traditional; insistence on prefix arose with the STL, a library for a language whose name contains, ironically, a postfix increment.

Why are there braces but no semicolons? And why can't I put the opening brace on the next line?

Go uses brace brackets for statement grouping, a syntax familiar to programmers who have worked with any language in the C family. Semicolons, however, are for parsers, not for people, and we wanted to eliminate them as much as possible. To achieve this goal, Go borrows a trick from BCPL: the semicolons that separate statements are in the formal grammar but are injected automatically, without lookahead, by the lexer at the end of any line that could be the end of a statement. This works very well in practice but has the effect that it forces a brace style. For instance, the opening brace of a function cannot appear on a line by itself.

Some have argued that the lexer should do lookahead to permit the brace to live on the next line. We disagree. Since Go code is meant to be formatted automatically by gofmtsome style must be chosen. That style may differ from what you've used in C or Java, but Go is a different language and gofmt's style is as good as any other. More important—much more important—the advantages of a single, programmatically mandated format for all Go programs greatly outweigh any perceived disadvantages of the particular style. Note too that Go's style means that an interactive implementation of Go can use the standard syntax one line at a time without special rules.

Why do garbage collection? Won't it be too expensive?

One of the biggest sources of bookkeeping in systems programs is managing the lifetimes of allocated objects. In languages such as C in which it is done manually, it can consume a significant amount of programmer time and is often the cause of pernicious bugs. Even in languages like C++ or Rust that provide mechanisms to assist, those mechanisms can have a significant effect on the design of the software, often adding programming overhead of its own. We felt it was critical to eliminate such programmer overheads, and advances in garbage collection technology in the last few years gave us confidence that it could be implemented cheaply enough, and with low enough latency, that it could be a viable approach for networked systems.

Much of the difficulty of concurrent programming has its roots in the object lifetime problem: as objects get passed among threads it becomes cumbersome to guarantee they become freed safely. Automatic garbage collection makes concurrent code far easier to write. Of course, implementing garbage collection in a concurrent environment is itself a challenge, but meeting it once rather than in every program helps everyone.

Finally, concurrency aside, garbage collection makes interfaces simpler because they don't need to specify how memory is managed across them.

This is not to say that the recent work in languages like Rust that bring new ideas to the problem of managing resources is misguided; we encourage this work and are excited to see how it evolves. But Go takes a more traditional approach by addressing object lifetimes through garbage collection, and garbage collection alone.

The current implementation is a mark-and-sweep collector. If the machine is a multiprocessor, the collector runs on a separate CPU core in parallel with the main program. Major work on the collector in recent years has reduced pause times often to the sub-millisecond range, even for large heaps, all but eliminating one of the major objections to garbage collection in networked servers. Work continues to refine the algorithm, reduce overhead and latency further, and to explore new approaches. The 2018 ISMM keynote by Rick Hudson of the Go team describes the progress so far and suggests some future approaches.

On the topic of performance, keep in mind that Go gives the programmer considerable control over memory layout and allocation, much more than is typical in garbage-collected languages. A careful programmer can reduce the garbage collection overhead dramatically by using the language well; see the article about profiling Go programs for a worked example, including a demonstration of Go's profiling tools.

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:

译文地址:https://learnku.com/go/wikis/38175

讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!