Golang的常见问题中英对照【三】

141 阅读15分钟

在Go语言学习的过程中,我们是否有一些关于语言本身的一些疑问?而一些基本问题的答案众说纷纭,没有一个标准。其实,Go语言官方提供了常见问题(Frequently Asked Questions)供开发者参考。

一切问题最权威的回答一定来自官方,该文档从使用者的角度去提问,从设计中的角度去解答,值得我们去阅读和学习。

该文档原文是英文的,为了方便自己和他人学习,笔者在这里把全部内容翻译出来,并使用中英对照的方式分享,希望对有需要的Go开发者有所帮助。

这是 Golang 的常见问题中英对照的第三篇,官方回答了 Go 设计中的一些问题,我们可以从中了解 Go 语言的语言特性、运行时、性能、并发实现等。

第一篇见Golang的常见问题中英对照【一】

第二篇见Golang的常见问题中英对照【二】

设计

Does Go have a runtime?

Go 支持运行时吗?

Go does have an extensive library, called the runtime, that is part of every Go program. The runtime library implements garbage collection, concurrency, stack management, and other critical features of the Go language. Although it is more central to the language, Go's runtime is analogous to libc, the C library.

Go 确实有一个广泛的库,称为运行时,它是每个 Go 程序的一部分。运行时库实现了垃圾回收、并发、堆栈管理和 Go 语言的其他关键功能。虽然它更核心于语言,但 Go 的运行时类似于 libc ( C 语言库)。

It is important to understand, however, that Go's runtime does not include a virtual machine, such as is provided by the Java runtime. Go programs are compiled ahead of time to native machine code (or JavaScript or WebAssembly, for some variant implementations). Thus, although the term is often used to describe the virtual environment in which a program runs, in Go the word “runtime” is just the name given to the library providing critical language services.

然而,重要的是要了解 Go 的运行时不包括虚拟机,例如由 Java 运行时提供的虚拟机。Go 程序会提前编译为本机机器代码(或 JavaScript 或 WebAssembly,对于某些变体实现)。因此,尽管该术语通常用于描述程序运行的虚拟环境,但在 Go 中,“运行时”一词只是提供关键语言服务的库的名称。

What's up with Unicode identifiers?

Unicode 标识符是怎么回事?

When designing Go, we wanted to make sure that it was not overly ASCII-centric, which meant extending the space of identifiers from the confines of 7-bit ASCII. Go's rule—identifier characters must be letters or digits as defined by Unicode—is simple to understand and to implement but has restrictions. Combining characters are excluded by design, for instance, and that excludes some languages such as Devanagari.

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

This rule has one other unfortunate consequence. Since an exported identifier must begin with an upper-case letter, identifiers created from characters in some languages can, by definition, not be exported. For now the only solution is to use something like X日本語, which is clearly unsatisfactory.

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

Since the earliest version of the language, there has been considerable thought into how best to expand the identifier space to accommodate programmers using other native languages. Exactly what to do remains an active topic of discussion, and a future version of the language may be more liberal in its definition of an identifier. For instance, it might adopt some of the ideas from the Unicode organization's recommendations for identifiers. Whatever happens, it must be done compatibly while preserving (or perhaps expanding) the way letter case determines visibility of identifiers, which remains one of our favorite features of Go.

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

For the time being, we have a simple rule that can be expanded later without breaking programs, one that avoids bugs that would surely arise from a rule that admits ambiguous identifiers.

目前,我们有一个简单的规则,可以在以后不破坏程序的情况下进行扩展,该规则可以避免因允许模糊标识符的规则而肯定会产生的错误。

Why does Go not have feature X?

为什么 Go 没有“功能 X ”

Every language contains novel features and omits someone's favorite feature. Go was designed with an eye on felicity of programming, speed of compilation, orthogonality of concepts, and the need to support features such as concurrency and garbage collection. Your favorite feature may be missing because it doesn't fit, because it affects compilation speed or clarity of design, or because it would make the fundamental system model too difficult.

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

If it bothers you that Go is missing feature X, please forgive us and investigate the features that Go does have. You might find that they compensate in interesting ways for the lack of X.

如果 Go 缺少“功能 X ”让您感到困扰,请原谅我们并查看 Go 确实具有的功能。你可能会发现它们以有趣的方式弥补了 X 的缺失。

When did Go get generic types?

Go 什么时候支持泛型?

The Go 1.18 release added type parameters to the language. This permits a form of polymorphic or generic programming. See the language spec and the proposal for details.

Go 1.18 版本为语言添加了类型参数。这允许一种形式的多态或泛型编程。有关详细信息,请参阅语言规范和提案。

Why was Go initially released without generic types?

为什么 Go 最初发布的时候不包含泛型?

Go was intended as a language for writing server programs that would be easy to maintain over time. (See this article for more background.) The design concentrated on things like scalability, readability, and concurrency. Polymorphic programming did not seem essential to the language's goals at the time, and so was initially left out for simplicity.

Go 旨在作为一种用于编写服务器程序的语言,随着时间的推移,这些程序易于维护。(有关更多背景信息,请参阅此文章。该设计侧重于可伸缩性、可读性和并发性等内容。当时,多态编程似乎对语言的目标并不重要,因此最初为了简单起见而被排除在外。

Generics are convenient but they come at a cost in complexity in the type system and run-time. It took a while to develop a design that we believe gives value proportionate to the complexity.

泛型很方便,但它们在类型系统和运行时的复杂性方面是有代价的。我们花了一段时间才开发出一种设计,我们相信这种设计的价值与复杂性成正比。

Why does Go not have exceptions?

为何 Go 没有异常处理?

We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional.

我们认为,将异常与控制结构耦合(如 try-catch-finally 语句中)会导致代码复杂。它还倾向于鼓励程序员将太多的普通错误(例如无法打开文件)标记为异常错误。

Go takes a different approach. For plain error handling, Go's multi-value returns make it easy to report an error without overloading the return value. A canonical error type, coupled with Go's other features, makes error handling pleasant but quite different from that in other languages.

Go 采取了不同的方式。对于简单的错误处理,Go 的多值返回可以轻松报告错误,而不会使返回值过载。规范错误类型,再加上其他功能,与其他语言完全不同,GO 使错误处理变得愉快。

Go also has a couple of built-in functions to signal and recover from truly exceptional conditions. The recovery mechanism is executed only as part of a function's state being torn down after an error, which is sufficient to handle catastrophe but requires no extra control structures and, when used well, can result in clean error-handling code.

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

See the Defer, Panic, and Recover article for details. Also, the Errors are values blog post describes one approach to handling errors cleanly in Go by demonstrating that, since errors are just values, the full power of Go can be deployed in error handling.

有关详细信息,请查看Defer, Panic, and Recover一文。此外,错误是值博客文章描述了一种在 Go 中干净地处理错误的方法,它演示了由于错误只是值,因此可以在错误处理中部署 Go 的全部功能。

Why does Go not have assertions?

为什么 Go 不支持断言?

Go doesn't provide assertions. They are undeniably convenient, but our experience has been that programmers use them as a crutch to avoid thinking about proper error handling and reporting. Proper error handling means that servers continue to operate instead of crashing after a non-fatal error. Proper error reporting means that errors are direct and to the point, saving the programmer from interpreting a large crash trace. Precise errors are particularly important when the programmer seeing the errors is not familiar with the code.

Go 不提供断言。不可否认,它们很方便,但我们的经验是,程序员将依赖它们以避免考虑正确的错误处理和报告。正确的错误处理意味着服务器继续运行,而不是在非致命错误后崩溃。正确的错误报告意味着错误是直接的和切中要害的,使程序员免于解释大型崩溃跟踪。当看到错误的程序员不熟悉代码时,精确的错误尤为重要。

We understand that this is a point of contention. There are many things in the Go language and libraries that differ from modern practices, simply because we feel it's sometimes worth trying a different approach.

我们理解这是一个争议点。Go 语言及其库中有很多东西与现代实践不同,仅仅是因为我们认为有时值得尝试不同的方法。

Why build concurrency on the ideas of CSP?

为什么要基于 CSP 的理念构建并发性?

Concurrency and multi-threaded programming have over time developed a reputation for difficulty. We believe this is due partly to complex designs such as pthreads and partly to overemphasis on low-level details such as mutexes, condition variables, and memory barriers. Higher-level interfaces enable much simpler code, even if there are still mutexes and such under the covers.

随着时间的流逝,并发和多线程编程已经成了困难的代名词。我们认为,这部分是由于复杂的设计(如pthreads),部分是由于过分强调低级细节,如互斥锁、条件变量和内存障碍。更高级别的接口使代码更简单,即使隐藏下仍有互斥锁等。

One of the most successful models for providing high-level linguistic support for concurrency comes from Hoare's Communicating Sequential Processes, or CSP. Occam and Erlang are two well known languages that stem from CSP. Go's concurrency primitives derive from a different part of the family tree whose main contribution is the powerful notion of channels as first class objects. Experience with several earlier languages has shown that the CSP model fits well into a procedural language framework.

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

Why goroutines instead of threads?

为什么使用 goroutines 替代 threads?

Goroutines are part of making concurrency easy to use. The idea, which has been around for a while, is to multiplex independently executing functions—coroutines—onto a set of threads. When a coroutine blocks, such as by calling a blocking system call, the run-time automatically moves other coroutines on the same operating system thread to a different, runnable thread so they won't be blocked. The programmer sees none of this, which is the point. The result, which we call goroutines, can be very cheap: they have little overhead beyond the memory for the stack, which is just a few kilobytes.

Goroutines 是使并发易于使用的一部分。这个想法已经存在了一段时间,是将独立执行的函数(协程)多路复用到一组线程上。当协程阻塞时(例如通过调用阻塞系统调用),运行时会自动将同一操作系统线程上的其他协程移动到不同的可运行线程,以便它们不会被阻塞。程序员看不到这些,这就是重点。结果,我们称之为 goroutines,可能消耗非常小:它们除了堆栈内存之外几乎没有开销,只有几 KB。

To make the stacks small, Go's run-time uses resizable, bounded stacks. A newly minted goroutine is given a few kilobytes, which is almost always enough. When it isn't, the run-time grows (and shrinks) the memory for storing the stack automatically, allowing many goroutines to live in a modest amount of memory. The CPU overhead averages about three cheap instructions per function call. It is practical to create hundreds of thousands of goroutines in the same address space. If goroutines were just threads, system resources would run out at a much smaller number.

为了使堆栈变小,Go 的运行时使用可调整大小的有界堆栈。一个新铸造的 goroutine 被赋予了几 KB,这几乎总是足够的。如果不是这样,运行时会自动增加(和缩小)用于存储堆栈的内存,从而允许许多 goroutine 存在于适度的内存中。CPU 开销平均每个函数调用大约三个廉价指令。在同一个地址空间中创建数十万个 goroutine 是可行的。如果 goroutines 只是线程,系统资源将以更少的数量耗尽。

Why are map operations not defined to be atomic?

为什么 map 的操作不被定义为原子性的?

After long discussion it was decided that the typical use of maps did not require safe access from multiple goroutines, and in those cases where it did, the map was probably part of some larger data structure or computation that was already synchronized. Therefore requiring that all map operations grab a mutex would slow down most programs and add safety to few. This was not an easy decision, however, since it means uncontrolled map access can crash the program.

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

The language does not preclude atomic map updates. When required, such as when hosting an untrusted program, the implementation could interlock map access.

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

Map access is unsafe only when updates are occurring. As long as all goroutines are only reading—looking up elements in the map, including iterating through it using a for range loop—and not changing the map by assigning to elements or doing deletions, it is safe for them to access the map concurrently without synchronization.

仅当发生更新时,map 访问才不安全。只要所有 goroutines 都只在读取(查找 map 中的元素,包括使用 for range 循环遍历它)而不是通过分配给元素或执行删除来更改 map ,它们就可以安全地同时访问 map 而无需同步。

As an aid to correct map use, some implementations of the language contain a special check that automatically reports at run time when a map is modified unsafely by concurrent execution.

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

Will you accept my language change?

你们会接受我的语言更改吗?

People often suggest improvements to the language—the mailing list contains a rich history of such discussions—but very few of these changes have been accepted.

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

Although Go is an open source project, the language and libraries are protected by a compatibility promise that prevents changes that break existing programs, at least at the source code level (programs may need to be recompiled occasionally to stay current). If your proposal violates the Go 1 specification we cannot even entertain the idea, regardless of its merit. A future major release of Go may be incompatible with Go 1, but discussions on that topic have only just begun and one thing is certain: there will be very few such incompatibilities introduced in the process. Moreover, the compatibility promise encourages us to provide an automatic path forward for old programs to adapt should that situation arise.

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

Even if your proposal is compatible with the Go 1 spec, it might not be in the spirit of Go's design goals. The article Go at Google: Language Design in the Service of Software Engineering explains Go's origins and the motivation behind its design.

即使您的提案与 Go 1 规范兼容,也可能不符合 Go 的设计目标。Go at Google: Language Design in the Service of Software Engineering 一文解释了 Go 的起源及其设计背后的动机。