这是用户在 2025-5-5 13:43 为 https://go.dev/doc/effective_go 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

Effective Go  有效的 Go

Introduction  介绍 ¶

Go is a new language. Although it borrows ideas from existing languages, it has unusual properties that make effective Go programs different in character from programs written in its relatives. A straightforward translation of a C++ or Java program into Go is unlikely to produce a satisfactory result—Java programs are written in Java, not Go. On the other hand, thinking about the problem from a Go perspective could produce a successful but quite different program. In other words, to write Go well, it's important to understand its properties and idioms. It's also important to know the established conventions for programming in Go, such as naming, formatting, program construction, and so on, so that programs you write will be easy for other Go programmers to understand.
Go 是一门新语言。虽然它借鉴了现有语言的思想,但它具有一些不同寻常的特性,使得高效的 Go 程序在风格上与其亲缘语言的程序不同。将 C++ 或 Java 程序直接翻译成 Go 程序不太可能得到令人满意的结果——Java 程序是用 Java 写的,而不是 Go。另一方面,从 Go 的角度思考问题可能会产生一个成功但截然不同的程序。换句话说,要写好 Go,理解它的特性和惯用法非常重要。同时,了解 Go 编程的既定约定,如命名、格式、程序构造等,也很重要,这样你写的程序才能让其他 Go 程序员容易理解。

This document gives tips for writing clear, idiomatic Go code. It augments the language specification, the Tour of Go, and How to Write Go Code, all of which you should read first.
本文档提供了编写清晰、惯用 Go 代码的技巧。它补充了语言规范、《Go 语言之旅》和《如何编写 Go 代码》,这些你都应该先阅读。

Note added January, 2022: This document was written for Go's release in 2009, and has not been updated significantly since. Although it is a good guide to understand how to use the language itself, thanks to the stability of the language, it says little about the libraries and nothing about significant changes to the Go ecosystem since it was written, such as the build system, testing, modules, and polymorphism. There are no plans to update it, as so much has happened and a large and growing set of documents, blogs, and books do a fine job of describing modern Go usage. Effective Go continues to be useful, but the reader should understand it is far from a complete guide. See issue 28782 for context.
2022 年 1 月补充说明:本文档最初为 2009 年 Go 语言发布时编写,自那以后未做重大更新。尽管它是理解如何使用语言本身的良好指南,得益于语言的稳定性,但它对库的介绍很少,也未涉及自编写以来 Go 生态系统的重大变化,如构建系统、测试、模块和多态性。没有计划更新本文档,因为已经发生了许多变化,且有大量不断增长的文档、博客和书籍很好地描述了现代 Go 的使用。Effective Go 仍然有用,但读者应理解它远非完整指南。有关背景信息,请参见 issue 28782。

Examples  示例 ¶

The Go package sources are intended to serve not only as the core library but also as examples of how to use the language. Moreover, many of the packages contain working, self-contained executable examples you can run directly from the go.dev web site, such as this one (if necessary, click on the word "Example" to open it up). If you have a question about how to approach a problem or how something might be implemented, the documentation, code and examples in the library can provide answers, ideas and background.
Go 包的源代码不仅旨在作为核心库,还作为如何使用该语言的示例。此外,许多包包含可直接从 go.dev 网站运行的工作且独立的可执行示例,比如这个(如有必要,点击“Example”一词以展开)。如果你对如何解决某个问题或某些实现方式有疑问,库中的文档、代码和示例可以提供答案、思路和背景。

Formatting  格式化 ¶

Formatting issues are the most contentious but the least consequential. People can adapt to different formatting styles but it's better if they don't have to, and less time is devoted to the topic if everyone adheres to the same style. The problem is how to approach this Utopia without a long prescriptive style guide.
格式化问题是最有争议但影响最小的。人们可以适应不同的格式风格,但如果不必适应会更好,如果每个人都遵循相同的风格,讨论这个话题的时间也会更少。问题在于如何在没有冗长规定性风格指南的情况下实现这种理想状态。

With Go we take an unusual approach and let the machine take care of most formatting issues. The gofmt program (also available as go fmt, which operates at the package level rather than source file level) reads a Go program and emits the source in a standard style of indentation and vertical alignment, retaining and if necessary reformatting comments. If you want to know how to handle some new layout situation, run gofmt; if the answer doesn't seem right, rearrange your program (or file a bug about gofmt), don't work around it.
使用 Go,我们采取了一种不同寻常的方法,让机器处理大多数格式问题。 gofmt 程序(也有一个版本是 go fmt ,它在包级别而非源文件级别操作)读取 Go 程序并以标准的缩进和垂直对齐样式输出源代码,保留并在必要时重新格式化注释。如果你想知道如何处理某种新的布局情况,运行 gofmt ;如果结果看起来不对,调整你的程序(或提交关于 gofmt 的错误报告),不要试图绕过它。

As an example, there's no need to spend time lining up the comments on the fields of a structure. Gofmt will do that for you. Given the declaration
例如,不需要花时间对齐结构体字段上的注释。 Gofmt 会为你完成这项工作。给定声明

type T struct {
    name string // name of the object
    value int // its value
}

gofmt will line up the columns:
gofmt 会对齐列:

type T struct {
    name    string // name of the object
    value   int    // its value
}

All Go code in the standard packages has been formatted with gofmt.
所有标准包中的 Go 代码都已经用 gofmt 格式化过。

Some formatting details remain. Very briefly:
一些格式细节仍然存在。简要说明:

Indentation  缩进
We use tabs for indentation and gofmt emits them by default. Use spaces only if you must.
我们使用制表符进行缩进, gofmt 默认会输出制表符。只有在必须时才使用空格。
Line length  行长度
Go has no line length limit. Don't worry about overflowing a punched card. If a line feels too long, wrap it and indent with an extra tab.
Go 没有行长度限制。无需担心穿孔卡片溢出。如果一行感觉太长,可以换行并用额外的制表符缩进。
Parentheses  括号
Go needs fewer parentheses than C and Java: control structures (if, for, switch) do not have parentheses in their syntax. Also, the operator precedence hierarchy is shorter and clearer, so
x<<8 + y<<16
means what the spacing implies, unlike in the other languages.
Go 需要的括号比 C 和 Java 少:控制结构( ifforswitch )的语法中没有括号。此外,运算符优先级层次更短且更清晰,因此其含义由空格决定,这与其他语言不同。

Commentary  注释 ¶

Go provides C-style /* */ block comments and C++-style // line comments. Line comments are the norm; block comments appear mostly as package comments, but are useful within an expression or to disable large swaths of code.
Go 提供了 C 风格的块注释和 C++ 风格的行注释。行注释是常规用法;块注释主要出现在包注释中,但在表达式内或禁用大量代码时也很有用。

Comments that appear before top-level declarations, with no intervening newlines, are considered to document the declaration itself. These “doc comments” are the primary documentation for a given Go package or command. For more about doc comments, see “Go Doc Comments”.
出现在顶级声明之前且没有中间空行的注释,被视为对该声明本身的文档说明。这些“文档注释”是给定 Go 包或命令的主要文档。有关文档注释的更多信息,请参见“Go 文档注释”。

Names  名称 ¶

Names are as important in Go as in any other language. They even have semantic effect: the visibility of a name outside a package is determined by whether its first character is upper case. It's therefore worth spending a little time talking about naming conventions in Go programs.
在 Go 语言中,名称和其他语言一样重要。它们甚至具有语义效果:一个名称在包外的可见性取决于其第一个字符是否为大写。因此,值得花点时间讨论 Go 程序中的命名约定。

Package names  包名 ¶

When a package is imported, the package name becomes an accessor for the contents. After
当一个包被导入时,包名就成为访问其内容的标识符。之后

import "bytes"

the importing package can talk about bytes.Buffer. It's helpful if everyone using the package can use the same name to refer to its contents, which implies that the package name should be good: short, concise, evocative. By convention, packages are given lower case, single-word names; there should be no need for underscores or mixedCaps. Err on the side of brevity, since everyone using your package will be typing that name. And don't worry about collisions a priori. The package name is only the default name for imports; it need not be unique across all source code, and in the rare case of a collision the importing package can choose a different name to use locally. In any case, confusion is rare because the file name in the import determines just which package is being used.
导入包可以通过 bytes.Buffer 来引用它。让所有使用该包的人都能用相同的名字来指代其内容是很有帮助的,这意味着包名应该简洁、明了且富有表现力。按照惯例,包名应为小写的单词;不应使用下划线或混合大小写。应倾向于简短,因为使用你包的每个人都需要输入这个名字。且不必事先担心命名冲突。包名只是导入时的默认名称;它不必在所有源代码中唯一,在极少数冲突的情况下,导入包可以选择一个不同的本地名称。无论如何,混淆很少见,因为导入时的文件名决定了具体使用的是哪个包。

Another convention is that the package name is the base name of its source directory; the package in src/encoding/base64 is imported as "encoding/base64" but has name base64, not encoding_base64 and not encodingBase64.
另一个约定是包名是其源目录的基本名称; src/encoding/base64 中的包被导入为 "encoding/base64" ,但名称是 base64 ,而不是 encoding_base64 也不是 encodingBase64

The importer of a package will use the name to refer to its contents, so exported names in the package can use that fact to avoid repetition. (Don't use the import . notation, which can simplify tests that must run outside the package they are testing, but should otherwise be avoided.) For instance, the buffered reader type in the bufio package is called Reader, not BufReader, because users see it as bufio.Reader, which is a clear, concise name. Moreover, because imported entities are always addressed with their package name, bufio.Reader does not conflict with io.Reader. Similarly, the function to make new instances of ring.Ring—which is the definition of a constructor in Go—would normally be called NewRing, but since Ring is the only type exported by the package, and since the package is called ring, it's called just New, which clients of the package see as ring.New. Use the package structure to help you choose good names.
包的导入者将使用该名称来引用其内容,因此包中的导出名称可以利用这一事实来避免重复。(不要使用 import . 符号,这可以简化必须在被测试包之外运行的测试,但应尽量避免使用。)例如, bufio 包中的缓冲读取器类型被称为 Reader ,而不是 BufReader ,因为用户将其视为 bufio.Reader ,这是一个清晰简洁的名称。此外,由于导入的实体总是通过其包名来访问, bufio.Reader 不会与 io.Reader 冲突。类似地,用于创建 ring.Ring 新实例的函数——这就是 Go 中构造函数的定义——通常称为 NewRing ,但由于 Ring 是该包唯一导出的类型,且包名为 ring ,因此它仅被称为 New ,包的客户端将其视为 ring.New 。利用包结构帮助你选择合适的名称。

Another short example is once.Do; once.Do(setup) reads well and would not be improved by writing once.DoOrWaitUntilDone(setup). Long names don't automatically make things more readable. A helpful doc comment can often be more valuable than an extra long name.
另一个简短的例子是 once.Doonce.Do(setup) 读起来很顺畅,写成 once.DoOrWaitUntilDone(setup) 并不会更好。长名字并不自动使事情更易读。一个有帮助的文档注释往往比一个超长的名字更有价值。

Getters  访问器 ¶

Go doesn't provide automatic support for getters and setters. There's nothing wrong with providing getters and setters yourself, and it's often appropriate to do so, but it's neither idiomatic nor necessary to put Get into the getter's name. If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner. The use of upper-case names for export provides the hook to discriminate the field from the method. A setter function, if needed, will likely be called SetOwner. Both names read well in practice:
Go 不会自动提供访问器和设置器的支持。自己提供访问器和设置器没有问题,而且通常也是合适的,但在访问器名称中加入 Get 既不符合惯用法,也没有必要。如果你有一个名为 owner (小写,未导出)的字段,访问器方法应该叫做 Owner (大写,导出),而不是 GetOwner 。使用大写名称进行导出提供了区分字段和方法的钩子。如果需要,设置器函数很可能会被称为 SetOwner 。这两个名称在实际使用中都很通顺:

owner := obj.Owner()
if owner != user {
    obj.SetOwner(user)
}

Interface names  接口名称 ¶

By convention, one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc.
按照惯例,单方法接口的命名方式是将方法名加上 -er 后缀或类似的修改来构成一个代理名词: ReaderWriterFormatterCloseNotifier 等。

There are a number of such names and it's productive to honor them and the function names they capture. Read, Write, Close, Flush, String and so on have canonical signatures and meanings. To avoid confusion, don't give your method one of those names unless it has the same signature and meaning. Conversely, if your type implements a method with the same meaning as a method on a well-known type, give it the same name and signature; call your string-converter method String not ToString.
有许多这样的名称,尊重它们及其所代表的函数名称是有益的。 ReadWriteCloseFlushString 等等都有规范的签名和含义。为了避免混淆,除非你的方法具有相同的签名和含义,否则不要使用这些名称。相反,如果你的类型实现了一个与知名类型的方法含义相同的方法,请使用相同的名称和签名;将你的字符串转换方法命名为 String 而不是 ToString

MixedCaps  混合大小写 ¶

Finally, the convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword names.
最后,Go 语言的惯例是使用 MixedCapsmixedCaps 而不是下划线来书写多词名称。

Semicolons  分号 ¶

Like C, Go's formal grammar uses semicolons to terminate statements, but unlike in C, those semicolons do not appear in the source. Instead the lexer uses a simple rule to insert semicolons automatically as it scans, so the input text is mostly free of them.
像 C 语言一样,Go 的正式语法使用分号来终止语句,但与 C 不同的是,这些分号不会出现在源代码中。相反,词法分析器在扫描时使用一个简单的规则自动插入分号,因此输入文本大多不包含分号。

The rule is this. If the last token before a newline is an identifier (which includes words like int and float64), a basic literal such as a number or string constant, or one of the tokens
规则是这样的。如果换行符前的最后一个标记是标识符(包括像 intfloat64 这样的词)、基本字面量(如数字或字符串常量),或者以下标记之一

break continue fallthrough return ++ -- ) }

the lexer always inserts a semicolon after the token. This could be summarized as, “if the newline comes after a token that could end a statement, insert a semicolon”.
词法分析器总是在标记后插入分号。可以总结为:“如果换行符出现在可以结束语句的标记之后,则插入分号”。

A semicolon can also be omitted immediately before a closing brace, so a statement such as
分号也可以在紧接着闭合大括号之前省略,因此像这样的语句

    go func() { for { dst <- <-src } }()

needs no semicolons. Idiomatic Go programs have semicolons only in places such as for loop clauses, to separate the initializer, condition, and continuation elements. They are also necessary to separate multiple statements on a line, should you write code that way.
不需要分号。惯用的 Go 程序只有在诸如 for 循环子句中,用于分隔初始化、条件和继续元素时才使用分号。如果你以这种方式编写代码,分号也用于分隔一行上的多个语句。

One consequence of the semicolon insertion rules is that you cannot put the opening brace of a control structure (if, for, switch, or select) on the next line. If you do, a semicolon will be inserted before the brace, which could cause unwanted effects. Write them like this
分号插入规则的一个结果是,不能将控制结构( ifforswitchselect )的左大括号放在下一行。如果这样做,分号会被插入到大括号前面,可能导致不想要的效果。应该这样写。

if i < f() {
    g()
}

not like this   不是这样的

if i < f()  // wrong!
{           // wrong!
    g()
}

Control structures  控制结构 ¶

The control structures of Go are related to those of C but differ in important ways. There is no do or while loop, only a slightly generalized for; switch is more flexible; if and switch accept an optional initialization statement like that of for; break and continue statements take an optional label to identify what to break or continue; and there are new control structures including a type switch and a multiway communications multiplexer, select. The syntax is also slightly different: there are no parentheses and the bodies must always be brace-delimited.
Go 的控制结构与 C 语言相关,但在重要方面有所不同。没有 dowhile 循环,只有稍微泛化的 forswitch 更加灵活; ifswitch 接受类似于 for 的可选初始化语句; breakcontinue 语句带有可选标签,用于标识要中断或继续的内容;还有新的控制结构,包括类型选择和多路通信多路复用器 select 。语法也略有不同:没有括号,代码块必须始终用大括号括起来。

If  如果 ¶

In Go a simple if looks like this:
在 Go 中,一个简单的 if 看起来像这样:

if x > 0 {
    return y
}

Mandatory braces encourage writing simple if statements on multiple lines. It's good style to do so anyway, especially when the body contains a control statement such as a return or break.
强制使用大括号鼓励将简单的 if 语句写成多行。这样做是良好的风格,尤其当代码块中包含控制语句如 returnbreak 时。

Since if and switch accept an initialization statement, it's common to see one used to set up a local variable.
由于 ifswitch 接受初始化语句,因此常见用法是在其中设置局部变量。

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

In the Go libraries, you'll find that when an if statement doesn't flow into the next statement—that is, the body ends in break, continue, goto, or return—the unnecessary else is omitted.
在 Go 库中,你会发现当一个 if 语句不会流入下一条语句时——也就是说,主体以 breakcontinuegotoreturn 结束——不必要的 else 会被省略。

f, err := os.Open(name)
if err != nil {
    return err
}
codeUsing(f)

This is an example of a common situation where code must guard against a sequence of error conditions. The code reads well if the successful flow of control runs down the page, eliminating error cases as they arise. Since error cases tend to end in return statements, the resulting code needs no else statements.
这是一个常见情况的示例,代码必须防范一系列错误条件。如果成功的控制流程顺序向下执行,逐一排除错误情况,代码会更易读。由于错误情况通常以 return 语句结束,生成的代码不需要 else 语句。

f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

Redeclaration and reassignment
重新声明和重新赋值 ¶

An aside: The last example in the previous section demonstrates a detail of how the := short declaration form works. The declaration that calls os.Open reads,
顺便说一句:上一节的最后一个例子演示了 := 短声明形式的一个细节。调用 os.Open 的声明是,

f, err := os.Open(name)

This statement declares two variables, f and err. A few lines later, the call to f.Stat reads,
该语句声明了两个变量, ferr 。几行之后,对 f.Stat 的调用读取,

d, err := f.Stat()

which looks as if it declares d and err. Notice, though, that err appears in both statements. This duplication is legal: err is declared by the first statement, but only re-assigned in the second. This means that the call to f.Stat uses the existing err variable declared above, and just gives it a new value.
看起来像是声明了 derr 。不过请注意, err 出现在两个语句中。这种重复是合法的: err 由第一个语句声明,但在第二个语句中只是重新赋值。这意味着对 f.Stat 的调用使用了上面声明的现有 err 变量,并只是给它赋了一个新值。

In a := declaration a variable v may appear even if it has already been declared, provided:
:= 声明中,即使变量 v 已经被声明,也可以出现,前提是:

This unusual property is pure pragmatism, making it easy to use a single err value, for example, in a long if-else chain. You'll see it used often.
这个不寻常的特性纯粹是务实的,使得使用单个 err 值变得容易,例如,在一个长的 if-else 链中。你会经常看到它的使用。

§ It's worth noting here that in Go the scope of function parameters and return values is the same as the function body, even though they appear lexically outside the braces that enclose the body.
值得注意的是,在 Go 语言中,函数参数和返回值的作用域与函数体相同,尽管它们在语法上出现在包围函数体的大括号之外。

For  对于¶

The Go for loop is similar to—but not the same as—C's. It unifies for and while and there is no do-while. There are three forms, only one of which has semicolons.
Go 的 for 循环类似于但不等同于 C 的循环。它统一了 forwhile ,并且没有 do-while 。有三种形式,只有一种带有分号。

// Like a C for
for init; condition; post { }

// Like a C while
for condition { }

// Like a C for(;;)
for { }

Short declarations make it easy to declare the index variable right in the loop.
短变量声明使得在循环中直接声明索引变量变得容易。

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

If you're looping over an array, slice, string, or map, or reading from a channel, a range clause can manage the loop.
如果你正在遍历数组、切片、字符串或映射,或者从通道读取, range 子句可以管理循环。

for key, value := range oldMap {
    newMap[key] = value
}

If you only need the first item in the range (the key or index), drop the second:
如果你只需要范围内的第一个元素(键或索引),可以忽略第二个元素:

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

If you only need the second item in the range (the value), use the blank identifier, an underscore, to discard the first:
如果你只需要范围内的第二个元素(值),可以使用空白标识符下划线来丢弃第一个元素:

sum := 0
for _, value := range array {
    sum += value
}

The blank identifier has many uses, as described in a later section.
空白标识符有许多用途,后面章节会详细说明。

For strings, the range does more work for you, breaking out individual Unicode code points by parsing the UTF-8. Erroneous encodings consume one byte and produce the replacement rune U+FFFD. (The name (with associated builtin type) rune is Go terminology for a single Unicode code point. See the language specification for details.) The loop
对于字符串, range 为你做了更多的工作,通过解析 UTF-8 来分解单个 Unicode 代码点。错误的编码会消耗一个字节并产生替代符号 U+FFFD。(名称(及其关联的内置类型) rune 是 Go 语言中表示单个 Unicode 代码点的术语。详情请参见语言规范。)循环

for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
    fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}

prints   打印

character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7

Finally, Go has no comma operator and ++ and -- are statements not expressions. Thus if you want to run multiple variables in a for you should use parallel assignment (although that precludes ++ and --).
最后,Go 没有逗号运算符, ++-- 是语句而非表达式。因此,如果你想在 for 中运行多个变量,应使用并行赋值(尽管这排除了 ++-- )。

// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

Switch  切换 ¶

Go's switch is more general than C's. The expressions need not be constants or even integers, the cases are evaluated top to bottom until a match is found, and if the switch has no expression it switches on true. It's therefore possible—and idiomatic—to write an if-else-if-else chain as a switch.
Go 的 switch 比 C 的更通用。表达式不必是常量甚至整数,case 从上到下依次求值直到找到匹配项,如果 switch 没有表达式,则切换到 true 。因此,可以且符合惯例地将 if - else - if - else 链写成一个 switch

func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    case 'A' <= c && c <= 'F':
        return c - 'A' + 10
    }
    return 0
}

There is no automatic fall through, but cases can be presented in comma-separated lists.
没有自动贯穿,但可以用逗号分隔的列表来表示多个 case。

func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
        return true
    }
    return false
}

Although they are not nearly as common in Go as some other C-like languages, break statements can be used to terminate a switch early. Sometimes, though, it's necessary to break out of a surrounding loop, not the switch, and in Go that can be accomplished by putting a label on the loop and "breaking" to that label. This example shows both uses.
虽然在 Go 语言中它们不像其他一些类似 C 的语言那样常见,但 break 语句可以用来提前终止 switch 。不过,有时需要跳出外层循环,而不是 switch,在 Go 中可以通过给循环加标签并“跳转”到该标签来实现。这个例子展示了这两种用法。

Loop:
    for n := 0; n < len(src); n += size {
        switch {
        case src[n] < sizeOne:
            if validateOnly {
                break
            }
            size = 1
            update(src[n])

        case src[n] < sizeTwo:
            if n+1 >= len(src) {
                err = errShortInput
                break Loop
            }
            if validateOnly {
                break
            }
            size = 2
            update(src[n] + src[n+1]<<shift)
        }
    }

Of course, the continue statement also accepts an optional label but it applies only to loops.
当然, continue 语句也接受一个可选标签,但它仅适用于循环。

To close this section, here's a comparison routine for byte slices that uses two switch statements:
为了结束本节,这里有一个使用两个 switch 语句的字节切片比较例程:

// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
    for i := 0; i < len(a) && i < len(b); i++ {
        switch {
        case a[i] > b[i]:
            return 1
        case a[i] < b[i]:
            return -1
        }
    }
    switch {
    case len(a) > len(b):
        return 1
    case len(a) < len(b):
        return -1
    }
    return 0
}

Type switch  类型开关 ¶

A switch can also be used to discover the dynamic type of an interface variable. Such a type switch uses the syntax of a type assertion with the keyword type inside the parentheses. If the switch declares a variable in the expression, the variable will have the corresponding type in each clause. It's also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different type in each case.
switch 也可以用来发现接口变量的动态类型。这样的类型 switch 使用带有关键字 type 的类型断言语法。如果 switch 在表达式中声明了一个变量,该变量在每个分支中将具有相应的类型。在这种情况下,复用变量名也是惯用做法,实际上是在每个分支中声明了一个具有相同名称但不同类型的新变量。

var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
    fmt.Printf("unexpected type %T\n", t)     // %T prints whatever type t has
case bool:
    fmt.Printf("boolean %t\n", t)             // t has type bool
case int:
    fmt.Printf("integer %d\n", t)             // t has type int
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}

Functions  函数 ¶

Multiple return values  多重返回值 ¶

One of Go's unusual features is that functions and methods can return multiple values. This form can be used to improve on a couple of clumsy idioms in C programs: in-band error returns such as -1 for EOF and modifying an argument passed by address.
Go 的一个不寻常的特性是函数和方法可以返回多个值。这种形式可以用来改进 C 程序中一些笨拙的习惯用法:如使用特定值表示错误(例如用 0 表示 1)以及通过地址修改传入的参数。

In C, a write error is signaled by a negative count with the error code secreted away in a volatile location. In Go, Write can return a count and an error: “Yes, you wrote some bytes but not all of them because you filled the device”. The signature of the Write method on files from package os is:
在 C 语言中,写入错误通过一个负数计数来表示,错误代码隐藏在一个易变的位置。在 Go 语言中, Write 可以返回一个计数和一个错误:“是的,你写入了一些字节,但不是全部,因为设备已满”。包 os 中文件的 Write 方法的签名是:

func (file *File) Write(b []byte) (n int, err error)

and as the documentation says, it returns the number of bytes written and a non-nil error when n != len(b). This is a common style; see the section on error handling for more examples.
正如文档所述,它返回写入的字节数和一个非空的 errorn != len(b) 。这是一种常见的风格;有关更多示例,请参见错误处理部分。

A similar approach obviates the need to pass a pointer to a return value to simulate a reference parameter. Here's a simple-minded function to grab a number from a position in a byte slice, returning the number and the next position.
类似的方法避免了传递指向返回值的指针来模拟引用参数的需要。这里有一个简单的函数,用于从字节切片中的某个位置获取一个数字,返回该数字和下一个位置。

func nextInt(b []byte, i int) (int, int) {
    for ; i < len(b) && !isDigit(b[i]); i++ {
    }
    x := 0
    for ; i < len(b) && isDigit(b[i]); i++ {
        x = x*10 + int(b[i]) - '0'
    }
    return x, i
}

You could use it to scan the numbers in an input slice b like this:
你可以用它来扫描输入切片 b 中的数字,方法如下:

    for i := 0; i < len(b); {
        x, i = nextInt(b, i)
        fmt.Println(x)
    }

Named result parameters
命名返回参数 ¶

The return or result "parameters" of a Go function can be given names and used as regular variables, just like the incoming parameters. When named, they are initialized to the zero values for their types when the function begins; if the function executes a return statement with no arguments, the current values of the result parameters are used as the returned values.
Go 函数的返回或结果“参数”可以被命名,并像传入参数一样作为普通变量使用。命名后,它们在函数开始时会被初始化为其类型的零值;如果函数执行一个无参数的 return 语句,则当前结果参数的值将作为返回值使用。

The names are not mandatory but they can make code shorter and clearer: they're documentation. If we name the results of nextInt it becomes obvious which returned int is which.
名称不是强制性的,但它们可以使代码更简洁、更清晰:它们也是一种文档。如果我们为 nextInt 命名结果,就能明显看出哪个返回的 int 是哪个。

func nextInt(b []byte, pos int) (value, nextPos int) {

Because named results are initialized and tied to an unadorned return, they can simplify as well as clarify. Here's a version of io.ReadFull that uses them well:
因为命名结果会被初始化并与无修饰的 return 绑定,它们既可以简化代码,也可以使代码更清晰。以下是一个很好地使用它们的 io.ReadFull 版本:

func ReadFull(r Reader, buf []byte) (n int, err error) {
    for len(buf) > 0 && err == nil {
        var nr int
        nr, err = r.Read(buf)
        n += nr
        buf = buf[nr:]
    }
    return
}

Defer  延迟 ¶

Go's defer statement schedules a function call (the deferred function) to be run immediately before the function executing the defer returns. It's an unusual but effective way to deal with situations such as resources that must be released regardless of which path a function takes to return. The canonical examples are unlocking a mutex or closing a file.
Go 的 defer 语句安排一个函数调用(延迟函数),该函数将在执行 defer 的函数返回之前立即运行。这是一种不寻常但有效的方式,用于处理必须释放资源的情况,无论函数以哪种路径返回。典型的例子是解锁互斥锁或关闭文件。

// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
    f, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer f.Close()  // f.Close will run when we're finished.

    var result []byte
    buf := make([]byte, 100)
    for {
        n, err := f.Read(buf[0:])
        result = append(result, buf[0:n]...) // append is discussed later.
        if err != nil {
            if err == io.EOF {
                break
            }
            return "", err  // f will be closed if we return here.
        }
    }
    return string(result), nil // f will be closed if we return here.
}

Deferring a call to a function such as Close has two advantages. First, it guarantees that you will never forget to close the file, a mistake that's easy to make if you later edit the function to add a new return path. Second, it means that the close sits near the open, which is much clearer than placing it at the end of the function.
将对函数如 Close 的调用延迟有两个好处。首先,它保证你永远不会忘记关闭文件,这种错误在你后来编辑函数以添加新的返回路径时很容易发生。其次,这意味着关闭操作靠近打开操作,这比将其放在函数末尾要清晰得多。

The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes. Besides avoiding worries about variables changing values as the function executes, this means that a single deferred call site can defer multiple function executions. Here's a silly example.
延迟函数的参数(如果函数是方法,则包括接收者)是在 defer 执行时求值的,而不是在调用执行时求值。除了避免担心函数执行过程中变量值的变化外,这意味着一个延迟调用点可以延迟多个函数的执行。这里有一个愚蠢的例子。

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

Deferred functions are executed in LIFO order, so this code will cause 4 3 2 1 0 to be printed when the function returns. A more plausible example is a simple way to trace function execution through the program. We could write a couple of simple tracing routines like this:
延迟函数按后进先出(LIFO)顺序执行,因此这段代码将在函数返回时打印 4 3 2 1 0 。一个更合理的例子是通过程序跟踪函数执行的简单方法。我们可以写几个简单的跟踪例程,如下所示:

func trace(s string)   { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }

// Use them like this:
func a() {
    trace("a")
    defer untrace("a")
    // do something....
}

We can do better by exploiting the fact that arguments to deferred functions are evaluated when the defer executes. The tracing routine can set up the argument to the untracing routine. This example:
我们可以通过利用延迟函数的参数在 defer 执行时被求值这一事实来做得更好。跟踪例程可以设置取消跟踪例程的参数。这个例子:

func trace(s string) string {
    fmt.Println("entering:", s)
    return s
}

func un(s string) {
    fmt.Println("leaving:", s)
}

func a() {
    defer un(trace("a"))
    fmt.Println("in a")
}

func b() {
    defer un(trace("b"))
    fmt.Println("in b")
    a()
}

func main() {
    b()
}

prints   打印

entering: b
in b
entering: a
in a
leaving: a
leaving: b

For programmers accustomed to block-level resource management from other languages, defer may seem peculiar, but its most interesting and powerful applications come precisely from the fact that it's not block-based but function-based. In the section on panic and recover we'll see another example of its possibilities.
对于习惯于其他语言中块级资源管理的程序员来说, defer 可能显得奇怪,但它最有趣和强大的应用恰恰来自于它不是基于块而是基于函数的这一事实。在 panicrecover 部分,我们将看到它可能性的另一个例子。

Data  数据 ¶

Allocation with new  使用 new 分配 ¶

Go has two allocation primitives, the built-in functions new and make. They do different things and apply to different types, which can be confusing, but the rules are simple. Let's talk about new first. It's a built-in function that allocates memory, but unlike its namesakes in some other languages it does not initialize the memory, it only zeros it. That is, new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T. In Go terminology, it returns a pointer to a newly allocated zero value of type T.
Go 有两个分配原语,内置函数 newmake 。它们的功能不同,适用于不同的类型,这可能会让人困惑,但规则很简单。我们先来谈谈 new 。它是一个内置函数,用于分配内存,但与某些其他语言中的同名函数不同,它不会初始化内存,只会将其清零。也就是说, new(T) 为类型为 T 的新项分配了清零的存储空间,并返回其地址,类型为 *T 的值。在 Go 术语中,它返回一个指向新分配的、类型为 T 的零值的指针。

Since the memory returned by new is zeroed, it's helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization. This means a user of the data structure can create one with new and get right to work. For example, the documentation for bytes.Buffer states that "the zero value for Buffer is an empty buffer ready to use." Similarly, sync.Mutex does not have an explicit constructor or Init method. Instead, the zero value for a sync.Mutex is defined to be an unlocked mutex.
由于 new 返回的内存已被清零,因此在设计数据结构时,将每种类型的零值设计为无需进一步初始化即可使用是很有帮助的。这意味着数据结构的使用者可以通过 new 创建一个实例并立即开始使用。例如, bytes.Buffer 的文档中指出“ Buffer 的零值是一个准备好使用的空缓冲区。”同样, sync.Mutex 没有显式的构造函数或 Init 方法。相反, sync.Mutex 的零值被定义为一个未锁定的互斥锁。

The zero-value-is-useful property works transitively. Consider this type declaration.
零值有用的特性是传递性的。考虑以下类型声明。

type SyncedBuffer struct {
    lock    sync.Mutex
    buffer  bytes.Buffer
}

Values of type SyncedBuffer are also ready to use immediately upon allocation or just declaration. In the next snippet, both p and v will work correctly without further arrangement.
类型为 SyncedBuffer 的值在分配或仅声明后也可以立即使用。在下面的代码片段中, pv 都可以正常工作,无需进一步安排。

p := new(SyncedBuffer)  // type *SyncedBuffer
var v SyncedBuffer      // type  SyncedBuffer

Constructors and composite literals
构造函数和复合字面量 ¶

Sometimes the zero value isn't good enough and an initializing constructor is necessary, as in this example derived from package os.
有时零值不够用,需要一个初始化构造函数,就像这个源自包 os 的例子。

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := new(File)
    f.fd = fd
    f.name = name
    f.dirinfo = nil
    f.nepipe = 0
    return f
}

There's a lot of boilerplate in there. We can simplify it using a composite literal, which is an expression that creates a new instance each time it is evaluated.
里面有很多样板代码。我们可以使用复合字面量来简化它,复合字面量是一种每次求值时都会创建新实例的表达式。

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := File{fd, name, nil, 0}
    return &f
}

Note that, unlike in C, it's perfectly OK to return the address of a local variable; the storage associated with the variable survives after the function returns. In fact, taking the address of a composite literal allocates a fresh instance each time it is evaluated, so we can combine these last two lines.
请注意,与 C 语言不同,返回局部变量的地址是完全可以的;与该变量相关的存储在函数返回后仍然存在。实际上,取复合字面量的地址每次都会分配一个新的实例,因此我们可以将最后两行合并。

    return &File{fd, name, nil, 0}

The fields of a composite literal are laid out in order and must all be present. However, by labeling the elements explicitly as field:value pairs, the initializers can appear in any order, with the missing ones left as their respective zero values. Thus we could say
复合字面量的字段按顺序排列,且必须全部存在。然而,通过将元素明确标记为字段名和值对,初始化器可以以任意顺序出现,缺失的字段将保留其各自的零值。因此我们可以说

    return &File{fd: fd, name: name}

As a limiting case, if a composite literal contains no fields at all, it creates a zero value for the type. The expressions new(File) and &File{} are equivalent.
作为极限情况,如果复合字面量根本不包含任何字段,它将创建该类型的零值。表达式 new(File)&File{} 是等价的。

Composite literals can also be created for arrays, slices, and maps, with the field labels being indices or map keys as appropriate. In these examples, the initializations work regardless of the values of Enone, Eio, and Einval, as long as they are distinct.
复合字面量也可以用于数组、切片和映射,字段标签分别是索引或映射键。在这些示例中,只要 EnoneEioEinval 不相同,初始化就能正常工作。

a := [...]string   {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
s := []string      {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}

Allocation with make  使用 make 进行分配 ¶

Back to allocation. The built-in function make(T, args) serves a purpose different from new(T). It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T). The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use. A slice, for example, is a three-item descriptor containing a pointer to the data (inside an array), the length, and the capacity, and until those items are initialized, the slice is nil. For slices, maps, and channels, make initializes the internal data structure and prepares the value for use. For instance,
回到分配。内置函数 make(T, args ) 的用途不同于 new(T) 。它只创建切片、映射和通道,并返回类型为 T 的已初始化(非零值)值(而非 *T )。之所以区分,是因为这三种类型在底层表示为必须初始化后才能使用的数据结构的引用。例如,切片是一个包含指向数据(数组内)的指针、长度和容量的三项描述符,在这些项未初始化之前,切片是 nil 。对于切片、映射和通道, make 初始化内部数据结构并准备该值以供使用。例如,

make([]int, 10, 100)

allocates an array of 100 ints and then creates a slice structure with length 10 and a capacity of 100 pointing at the first 10 elements of the array. (When making a slice, the capacity can be omitted; see the section on slices for more information.) In contrast, new([]int) returns a pointer to a newly allocated, zeroed slice structure, that is, a pointer to a nil slice value.
分配一个包含 100 个整数的数组,然后创建一个长度为 10、容量为 100 的切片结构,指向数组的前 10 个元素。(创建切片时,容量可以省略;更多信息请参见切片部分。)相比之下, new([]int) 返回一个指向新分配的、已清零的切片结构的指针,即指向一个 nil 切片值的指针。

These examples illustrate the difference between new and make.
这些例子说明了 newmake 之间的区别。

var p *[]int = new([]int)       // allocates slice structure; *p == nil; rarely useful
var v  []int = make([]int, 100) // the slice v now refers to a new array of 100 ints

// Unnecessarily complex:
var p *[]int = new([]int)
*p = make([]int, 100, 100)

// Idiomatic:
v := make([]int, 100)

Remember that make applies only to maps, slices and channels and does not return a pointer. To obtain an explicit pointer allocate with new or take the address of a variable explicitly.
请记住, make 仅适用于映射、切片和通道,并且不会返回指针。要获得显式指针,请使用 new 分配或显式获取变量的地址。

Arrays  数组 ¶

Arrays are useful when planning the detailed layout of memory and sometimes can help avoid allocation, but primarily they are a building block for slices, the subject of the next section. To lay the foundation for that topic, here are a few words about arrays.
数组在规划内存的详细布局时非常有用,有时可以帮助避免分配,但它们主要是切片的构建块,切片是下一节的主题。为了为该主题奠定基础,这里先介绍一些关于数组的内容。

There are major differences between the ways arrays work in Go and C. In Go,
Go 和 C 中数组的工作方式有很大不同。在 Go 中,

The value property can be useful but also expensive; if you want C-like behavior and efficiency, you can pass a pointer to the array.
value 属性可能有用,但也可能代价高昂;如果你想要类似 C 的行为和效率,可以传递数组的指针。

func Sum(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array)  // Note the explicit address-of operator

But even this style isn't idiomatic Go. Use slices instead.
但即使这种风格也不是惯用的 Go 语言写法。请改用切片。

Slices  切片 ¶

Slices wrap arrays to give a more general, powerful, and convenient interface to sequences of data. Except for items with explicit dimension such as transformation matrices, most array programming in Go is done with slices rather than simple arrays.
切片封装数组,提供了一个更通用、更强大且更方便的数据序列接口。除了具有明确维度的项目(如变换矩阵)外,Go 中的大多数数组编程都是使用切片而非简单数组完成的。

Slices hold references to an underlying array, and if you assign one slice to another, both refer to the same array. If a function takes a slice argument, changes it makes to the elements of the slice will be visible to the caller, analogous to passing a pointer to the underlying array. A Read function can therefore accept a slice argument rather than a pointer and a count; the length within the slice sets an upper limit of how much data to read. Here is the signature of the Read method of the File type in package os:
切片持有对底层数组的引用,如果你将一个切片赋值给另一个切片,两个切片都会引用同一个数组。如果一个函数接受一个切片参数,它对切片元素所做的更改对调用者是可见的,这类似于传递底层数组的指针。因此,一个 Read 函数可以接受一个切片参数,而不是指针和计数;切片中的长度设置了读取数据的上限。以下是包 osFile 类型的 Read 方法的签名:

func (f *File) Read(buf []byte) (n int, err error)

The method returns the number of bytes read and an error value, if any. To read into the first 32 bytes of a larger buffer buf, slice (here used as a verb) the buffer.
该方法返回读取的字节数和错误值(如果有)。要读取到较大缓冲区 buf 的前 32 个字节,请对缓冲区进行切片(此处用作动词)。

    n, err := f.Read(buf[0:32])

Such slicing is common and efficient. In fact, leaving efficiency aside for the moment, the following snippet would also read the first 32 bytes of the buffer.
这种切片操作既常见又高效。事实上,暂且不考虑效率,以下代码片段也会读取缓冲区的前 32 个字节。

    var n int
    var err error
    for i := 0; i < 32; i++ {
        nbytes, e := f.Read(buf[i:i+1])  // Read one byte.
        n += nbytes
        if nbytes == 0 || e != nil {
            err = e
            break
        }
    }

The length of a slice may be changed as long as it still fits within the limits of the underlying array; just assign it to a slice of itself. The capacity of a slice, accessible by the built-in function cap, reports the maximum length the slice may assume. Here is a function to append data to a slice. If the data exceeds the capacity, the slice is reallocated. The resulting slice is returned. The function uses the fact that len and cap are legal when applied to the nil slice, and return 0.
切片的长度可以更改,只要它仍然适合底层数组的限制;只需将其赋值为自身的一个切片。切片的容量可以通过内置函数 cap 访问,它报告切片可能达到的最大长度。这里有一个向切片追加数据的函数。如果数据超过容量,切片将被重新分配。返回结果切片。该函数利用了 lencapnil 切片上是合法的,并返回 0。

func Append(slice, data []byte) []byte {
    l := len(slice)
    if l + len(data) > cap(slice) {  // reallocate
        // Allocate double what's needed, for future growth.
        newSlice := make([]byte, (l+len(data))*2)
        // The copy function is predeclared and works for any slice type.
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:l+len(data)]
    copy(slice[l:], data)
    return slice
}

We must return the slice afterwards because, although Append can modify the elements of slice, the slice itself (the run-time data structure holding the pointer, length, and capacity) is passed by value.
我们必须随后返回切片,因为虽然 Append 可以修改 slice 的元素,但切片本身(包含指针、长度和容量的运行时数据结构)是按值传递的。

The idea of appending to a slice is so useful it's captured by the append built-in function. To understand that function's design, though, we need a little more information, so we'll return to it later.
向切片追加元素的想法非常有用,因此被内置函数 append 捕获。要理解该函数的设计,我们还需要更多信息,所以稍后会回过头来讲。

Two-dimensional slices  二维切片 ¶

Go's arrays and slices are one-dimensional. To create the equivalent of a 2D array or slice, it is necessary to define an array-of-arrays or slice-of-slices, like this:
Go 的数组和切片是单维的。要创建等同于二维数组或切片的结构,必须定义数组的数组或切片的切片,像这样:

type Transform [3][3]float64  // A 3x3 array, really an array of arrays.
type LinesOfText [][]byte     // A slice of byte slices.

Because slices are variable-length, it is possible to have each inner slice be a different length. That can be a common situation, as in our LinesOfText example: each line has an independent length.
因为切片是可变长度的,所以每个内部切片可以有不同的长度。这种情况很常见,比如我们的 LinesOfText 示例:每一行的长度都是独立的。

text := LinesOfText{
    []byte("Now is the time"),
    []byte("for all good gophers"),
    []byte("to bring some fun to the party."),
}

Sometimes it's necessary to allocate a 2D slice, a situation that can arise when processing scan lines of pixels, for instance. There are two ways to achieve this. One is to allocate each slice independently; the other is to allocate a single array and point the individual slices into it. Which to use depends on your application. If the slices might grow or shrink, they should be allocated independently to avoid overwriting the next line; if not, it can be more efficient to construct the object with a single allocation. For reference, here are sketches of the two methods. First, a line at a time:
有时需要分配一个二维切片,比如在处理像素扫描线时可能会遇到这种情况。有两种方法可以实现这一点。一种是独立分配每个切片;另一种是分配一个单一的数组,并将各个切片指向它。使用哪种方法取决于你的应用。如果切片可能会增长或缩小,应独立分配以避免覆盖下一行;如果不会,使用单次分配构造对象可能更高效。作为参考,下面是这两种方法的示意图。首先,一次处理一行:

// Allocate the top-level slice.
picture := make([][]uint8, YSize) // One row per unit of y.
// Loop over the rows, allocating the slice for each row.
for i := range picture {
    picture[i] = make([]uint8, XSize)
}

And now as one allocation, sliced into lines:
现在作为一次分配,切分成多行:

// Allocate the top-level slice, the same as before.
picture := make([][]uint8, YSize) // One row per unit of y.
// Allocate one large slice to hold all the pixels.
pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.
// Loop over the rows, slicing each row from the front of the remaining pixels slice.
for i := range picture {
    picture[i], pixels = pixels[:XSize], pixels[XSize:]
}

Maps  映射 ¶

Maps are a convenient and powerful built-in data structure that associate values of one type (the key) with values of another type (the element or value). The key can be of any type for which the equality operator is defined, such as integers, floating point and complex numbers, strings, pointers, interfaces (as long as the dynamic type supports equality), structs and arrays. Slices cannot be used as map keys, because equality is not defined on them. Like slices, maps hold references to an underlying data structure. If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller.
映射是一种方便且强大的内置数据结构,用于将一种类型的值(键)与另一种类型的值(元素或值)关联起来。键可以是任何定义了相等运算符的类型,例如整数、浮点数和复数、字符串、指针、接口(只要动态类型支持相等)、结构体和数组。切片不能用作映射的键,因为它们没有定义相等性。与切片类似,映射持有对底层数据结构的引用。如果你将映射传递给一个修改映射内容的函数,修改将在调用者中可见。

Maps can be constructed using the usual composite literal syntax with colon-separated key-value pairs, so it's easy to build them during initialization.
映射可以使用常规的复合字面量语法构造,键值对之间用冒号分隔,因此在初始化时构建它们很容易。

var timeZone = map[string]int{
    "UTC":  0*60*60,
    "EST": -5*60*60,
    "CST": -6*60*60,
    "MST": -7*60*60,
    "PST": -8*60*60,
}

Assigning and fetching map values looks syntactically just like doing the same for arrays and slices except that the index doesn't need to be an integer.
给映射赋值和获取值的语法看起来就像对数组和切片进行相同操作,只是索引不需要是整数。

offset := timeZone["EST"]

An attempt to fetch a map value with a key that is not present in the map will return the zero value for the type of the entries in the map. For instance, if the map contains integers, looking up a non-existent key will return 0. A set can be implemented as a map with value type bool. Set the map entry to true to put the value in the set, and then test it by simple indexing.
尝试使用地图中不存在的键获取地图值将返回地图中条目类型的零值。例如,如果地图包含整数,查找不存在的键将返回 0。集合可以实现为值类型为 1 的地图。将地图条目设置为 2 以将值放入集合中,然后通过简单索引进行测试。

attended := map[string]bool{
    "Ann": true,
    "Joe": true,
    ...
}

if attended[person] { // will be false if person is not in the map
    fmt.Println(person, "was at the meeting")
}

Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for "UTC" or is that 0 because it's not in the map at all? You can discriminate with a form of multiple assignment.
有时你需要区分缺失的条目和零值。 "UTC" 是否有条目,还是因为根本不在映射中而显示为 0?你可以通过一种多重赋值的形式来区分。

var seconds int
var ok bool
seconds, ok = timeZone[tz]

For obvious reasons this is called the “comma ok” idiom. In this example, if tz is present, seconds will be set appropriately and ok will be true; if not, seconds will be set to zero and ok will be false. Here's a function that puts it together with a nice error report:
出于显而易见的原因,这被称为“逗号 ok”惯用法。在这个例子中,如果 tz 存在, seconds 将被适当设置且 ok 为真;如果不存在, seconds 将被设为零且 ok 为假。这里有一个函数将其结合起来并提供了一个不错的错误报告:

func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    log.Println("unknown time zone:", tz)
    return 0
}

To test for presence in the map without worrying about the actual value, you can use the blank identifier (_) in place of the usual variable for the value.
要测试映射中是否存在某个键而不关心实际值,可以使用空白标识符( _ )代替通常用于值的变量。

_, present := timeZone[tz]

To delete a map entry, use the delete built-in function, whose arguments are the map and the key to be deleted. It's safe to do this even if the key is already absent from the map.
要删除映射条目,使用内置函数 delete ,其参数是映射和要删除的键。即使键已不存在于映射中,这样做也是安全的。

delete(timeZone, "PDT")  // Now on Standard Time

Printing  打印 ¶

Formatted printing in Go uses a style similar to C's printf family but is richer and more general. The functions live in the fmt package and have capitalized names: fmt.Printf, fmt.Fprintf, fmt.Sprintf and so on. The string functions (Sprintf etc.) return a string rather than filling in a provided buffer.
Go 语言中的格式化打印使用类似于 C 语言 printf 家族的风格,但更丰富且更通用。这些函数位于 fmt 包中,且名称首字母大写: fmt.Printffmt.Fprintffmt.Sprintf 等等。字符串函数(如 Sprintf 等)返回字符串,而不是填充提供的缓冲区。

You don't need to provide a format string. For each of Printf, Fprintf and Sprintf there is another pair of functions, for instance Print and Println. These functions do not take a format string but instead generate a default format for each argument. The Println versions also insert a blank between arguments and append a newline to the output while the Print versions add blanks only if the operand on neither side is a string. In this example each line produces the same output.
你不需要提供格式字符串。对于 PrintfFprintfSprintf ,还有另一对函数,例如 PrintPrintln 。这些函数不接受格式字符串,而是为每个参数生成默认格式。 Println 版本还会在参数之间插入空格,并在输出末尾添加换行符,而 Print 版本仅在操作数两侧都不是字符串时添加空格。在此示例中,每行产生相同的输出。

fmt.Printf("Hello %d\n", 23)
fmt.Fprint(os.Stdout, "Hello ", 23, "\n")
fmt.Println("Hello", 23)
fmt.Println(fmt.Sprint("Hello ", 23))

The formatted print functions fmt.Fprint and friends take as a first argument any object that implements the io.Writer interface; the variables os.Stdout and os.Stderr are familiar instances.
格式化打印函数 fmt.Fprint 及其相关函数的第一个参数可以是实现了 io.Writer 接口的任何对象;变量 os.Stdoutos.Stderr 是常见的实例。

Here things start to diverge from C. First, the numeric formats such as %d do not take flags for signedness or size; instead, the printing routines use the type of the argument to decide these properties.
这里的情况开始与 C 语言有所不同。首先,诸如 %d 之类的数字格式不接受表示符号或大小的标志;相反,打印例程使用参数的类型来决定这些属性。

var x uint64 = 1<<64 - 1
fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x))

prints   打印

18446744073709551615 ffffffffffffffff; -1 -1

If you just want the default conversion, such as decimal for integers, you can use the catchall format %v (for “value”); the result is exactly what Print and Println would produce. Moreover, that format can print any value, even arrays, slices, structs, and maps. Here is a print statement for the time zone map defined in the previous section.
如果你只想要默认的转换,比如整数的十进制,可以使用通用格式 %v (表示“值”);结果与 PrintPrintln 完全相同。此外,该格式可以打印任何值,甚至是数组、切片、结构体和映射。下面是上一节定义的时区映射的打印语句。

fmt.Printf("%v\n", timeZone)  // or just fmt.Println(timeZone)

which gives output:   其输出为:

map[CST:-21600 EST:-18000 MST:-25200 PST:-28800 UTC:0]

For maps, Printf and friends sort the output lexicographically by key.
对于映射, Printf 和相关函数按键的字典序对输出进行排序。

When printing a struct, the modified format %+v annotates the fields of the structure with their names, and for any value the alternate format %#v prints the value in full Go syntax.
打印结构体时,修改后的格式 %+v 会用字段名注释结构体的字段,而对于任何值,备用格式 %#v 会以完整的 Go 语法打印该值。

type T struct {
    a int
    b float64
    c string
}
t := &T{ 7, -2.35, "abc\tdef" }
fmt.Printf("%v\n", t)
fmt.Printf("%+v\n", t)
fmt.Printf("%#v\n", t)
fmt.Printf("%#v\n", timeZone)

prints   打印

&{7 -2.35 abc   def}
&{a:7 b:-2.35 c:abc     def}
&main.T{a:7, b:-2.35, c:"abc\tdef"}
map[string]int{"CST":-21600, "EST":-18000, "MST":-25200, "PST":-28800, "UTC":0}

(Note the ampersands.) That quoted string format is also available through %q when applied to a value of type string or []byte. The alternate format %#q will use backquotes instead if possible. (The %q format also applies to integers and runes, producing a single-quoted rune constant.) Also, %x works on strings, byte arrays and byte slices as well as on integers, generating a long hexadecimal string, and with a space in the format (% x) it puts spaces between the bytes.
(注意&符号。)当应用于类型为 string[]byte 的值时,该带引号的字符串格式也可以通过 %q 获得。备用格式 %#q 如果可能,将使用反引号。( %q 格式也适用于整数和 rune,生成单引号的 rune 常量。)此外, %x 适用于字符串、字节数组和字节切片以及整数,生成长的十六进制字符串,在格式( % x )中带空格时,会在字节之间添加空格。

Another handy format is %T, which prints the type of a value.
另一个方便的格式是 %T ,它打印一个值的类型。

fmt.Printf("%T\n", timeZone)

prints   打印

map[string]int

If you want to control the default format for a custom type, all that's required is to define a method with the signature String() string on the type. For our simple type T, that might look like this.
如果你想控制自定义类型的默认格式,只需在该类型上定义一个签名为 String() string 的方法。对于我们的简单类型 T ,可能看起来像这样。

func (t *T) String() string {
    return fmt.Sprintf("%d/%g/%q", t.a, t.b, t.c)
}
fmt.Printf("%v\n", t)

to print in the format
以格式打印

7/-2.35/"abc\tdef"

(If you need to print values of type T as well as pointers to T, the receiver for String must be of value type; this example used a pointer because that's more efficient and idiomatic for struct types. See the section below on pointers vs. value receivers for more information.)
(如果你需要打印类型为 T 的值以及指向 T 的指针, String 的接收者必须是值类型;这个例子使用指针是因为对于结构体类型来说,这样更高效且符合惯用法。有关指针接收者与值接收者的更多信息,请参见下面关于指针与值接收者的章节。)

Our String method is able to call Sprintf because the print routines are fully reentrant and can be wrapped this way. There is one important detail to understand about this approach, however: don't construct a String method by calling Sprintf in a way that will recur into your String method indefinitely. This can happen if the Sprintf call attempts to print the receiver directly as a string, which in turn will invoke the method again. It's a common and easy mistake to make, as this example shows.
我们的 String 方法能够调用 Sprintf ,因为打印例程是完全可重入的,并且可以这样包装。然而,有一个重要的细节需要理解:不要通过调用 Sprintf 来构造一个 String 方法,这样会导致无限递归调用你的 String 方法。如果 Sprintf 调用尝试直接将接收者作为字符串打印,这将再次调用该方法。这是一个常见且容易犯的错误,正如这个例子所示。

type MyString string

func (m MyString) String() string {
    return fmt.Sprintf("MyString=%s", m) // Error: will recur forever.
}

It's also easy to fix: convert the argument to the basic string type, which does not have the method.
这也很容易修复:将参数转换为基本字符串类型,该类型没有该方法。

type MyString string
func (m MyString) String() string {
    return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion.
}

In the initialization section we'll see another technique that avoids this recursion.
在初始化部分,我们将看到另一种避免这种递归的技术。

Another printing technique is to pass a print routine's arguments directly to another such routine. The signature of Printf uses the type ...interface{} for its final argument to specify that an arbitrary number of parameters (of arbitrary type) can appear after the format.
另一种打印技术是将打印例程的参数直接传递给另一个类似的例程。 Printf 的签名使用类型 ...interface{} 作为其最后一个参数,以指定格式后可以出现任意数量(任意类型)的参数。

func Printf(format string, v ...interface{}) (n int, err error) {

Within the function Printf, v acts like a variable of type []interface{} but if it is passed to another variadic function, it acts like a regular list of arguments. Here is the implementation of the function log.Println we used above. It passes its arguments directly to fmt.Sprintln for the actual formatting.
在函数 Printf 内, v 表现得像一个类型为 []interface{} 的变量,但如果它被传递给另一个可变参数函数,它则表现得像一个普通的参数列表。以下是我们上面使用的函数 log.Println 的实现。它将其参数直接传递给 fmt.Sprintln 进行实际格式化。

// Println prints to the standard logger in the manner of fmt.Println.
func Println(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))  // Output takes parameters (int, string)
}

We write ... after v in the nested call to Sprintln to tell the compiler to treat v as a list of arguments; otherwise it would just pass v as a single slice argument.
我们在嵌套调用 Sprintln 中的 v 后写 ... ,告诉编译器将 v 视为参数列表;否则它只会将 v 作为单个切片参数传递。

There's even more to printing than we've covered here. See the godoc documentation for package fmt for the details.
打印的内容远不止我们这里所涵盖的。详情请参阅 godoc 包的文档 fmt

By the way, a ... parameter can be of a specific type, for instance ...int for a min function that chooses the least of a list of integers:
顺便说一下, ... 参数可以是特定类型,例如用于选择整数列表中最小值的 min 函数的 ...int

func Min(a ...int) int {
    min := int(^uint(0) >> 1)  // largest int
    for _, i := range a {
        if i < min {
            min = i
        }
    }
    return min
}

Append  追加 ¶

Now we have the missing piece we needed to explain the design of the append built-in function. The signature of append is different from our custom Append function above. Schematically, it's like this:
现在我们有了缺失的部分,可以解释内置函数 append 的设计。 append 的签名与我们上面自定义的 Append 函数不同。示意图如下:

func append(slice []T, elements ...T) []T

where T is a placeholder for any given type. You can't actually write a function in Go where the type T is determined by the caller. That's why append is built in: it needs support from the compiler.
其中 T 是任何给定类型的占位符。你实际上不能在 Go 中编写一个函数,其类型 T 由调用者决定。这就是为什么 append 是内置的:它需要编译器的支持。

What append does is append the elements to the end of the slice and return the result. The result needs to be returned because, as with our hand-written Append, the underlying array may change. This simple example
append 的作用是将元素追加到切片的末尾并返回结果。需要返回结果是因为,正如我们手写的 Append 一样,底层数组可能会发生变化。这个简单的例子

x := []int{1,2,3}
x = append(x, 4, 5, 6)
fmt.Println(x)

prints [1 2 3 4 5 6]. So append works a little like Printf, collecting an arbitrary number of arguments.
打印 [1 2 3 4 5 6] 。所以 append 有点像 Printf ,收集任意数量的参数。

But what if we wanted to do what our Append does and append a slice to a slice? Easy: use ... at the call site, just as we did in the call to Output above. This snippet produces identical output to the one above.
但是如果我们想做我们的 Append 所做的事情,将一个切片追加到另一个切片呢?很简单:在调用处使用 ... ,就像我们在上面调用 Output 时所做的那样。这个代码片段产生的输出与上面完全相同。

x := []int{1,2,3}
y := []int{4,5,6}
x = append(x, y...)
fmt.Println(x)

Without that ..., it wouldn't compile because the types would be wrong; y is not of type int.
没有那个 ... ,代码无法编译,因为类型会错误; y 不是 int 类型。

Initialization  初始化 ¶

Although it doesn't look superficially very different from initialization in C or C++, initialization in Go is more powerful. Complex structures can be built during initialization and the ordering issues among initialized objects, even among different packages, are handled correctly.
虽然表面上看起来与 C 或 C++ 中的初始化没有太大区别,但 Go 中的初始化更加强大。复杂的结构可以在初始化期间构建,并且初始化对象之间的顺序问题,即使是在不同包之间,也能被正确处理。

Constants  常量 ¶

Constants in Go are just that—constant. They are created at compile time, even when defined as locals in functions, and can only be numbers, characters (runes), strings or booleans. Because of the compile-time restriction, the expressions that define them must be constant expressions, evaluatable by the compiler. For instance, 1<<3 is a constant expression, while math.Sin(math.Pi/4) is not because the function call to math.Sin needs to happen at run time.
Go 中的常量就是常量。它们在编译时创建,即使定义在函数的局部变量中,也只能是数字、字符(rune)、字符串或布尔值。由于编译时的限制,定义它们的表达式必须是编译器可计算的常量表达式。例如, 1<<3 是一个常量表达式,而 math.Sin(math.Pi/4) 不是,因为对 math.Sin 的函数调用必须在运行时发生。

In Go, enumerated constants are created using the iota enumerator. Since iota can be part of an expression and expressions can be implicitly repeated, it is easy to build intricate sets of values.
在 Go 语言中,枚举常量是使用 iota 枚举器创建的。由于 iota 可以作为表达式的一部分,且表达式可以被隐式重复,因此构建复杂的值集合变得很容易。

type ByteSize float64

const (
    _           = iota // ignore first value by assigning to blank identifier
    KB ByteSize = 1 << (10 * iota)
    MB
    GB
    TB
    PB
    EB
    ZB
    YB
)

The ability to attach a method such as String to any user-defined type makes it possible for arbitrary values to format themselves automatically for printing. Although you'll see it most often applied to structs, this technique is also useful for scalar types such as floating-point types like ByteSize.
将方法如 String 附加到任何用户定义类型的能力,使任意值能够自动格式化自身以供打印。虽然你最常见的是将其应用于结构体,但这种技术对于标量类型(如浮点类型 ByteSize )也很有用。

func (b ByteSize) String() string {
    switch {
    case b >= YB:
        return fmt.Sprintf("%.2fYB", b/YB)
    case b >= ZB:
        return fmt.Sprintf("%.2fZB", b/ZB)
    case b >= EB:
        return fmt.Sprintf("%.2fEB", b/EB)
    case b >= PB:
        return fmt.Sprintf("%.2fPB", b/PB)
    case b >= TB:
        return fmt.Sprintf("%.2fTB", b/TB)
    case b >= GB:
        return fmt.Sprintf("%.2fGB", b/GB)
    case b >= MB:
        return fmt.Sprintf("%.2fMB", b/MB)
    case b >= KB:
        return fmt.Sprintf("%.2fKB", b/KB)
    }
    return fmt.Sprintf("%.2fB", b)
}

The expression YB prints as 1.00YB, while ByteSize(1e13) prints as 9.09TB.
表达式 YB 打印为 1.00YB ,而 ByteSize(1e13) 打印为 9.09TB

The use here of Sprintf to implement ByteSize's String method is safe (avoids recurring indefinitely) not because of a conversion but because it calls Sprintf with %f, which is not a string format: Sprintf will only call the String method when it wants a string, and %f wants a floating-point value.
这里使用 Sprintf 来实现 ByteSizeString 方法是安全的(避免无限递归),不是因为类型转换,而是因为它调用了带有 %fSprintf ,这不是字符串格式: Sprintf 只有在需要字符串时才会调用 String 方法,而 %f 需要的是浮点值。

Variables  变量 ¶

Variables can be initialized just like constants but the initializer can be a general expression computed at run time.
变量可以像常量一样初始化,但初始化器可以是运行时计算的一般表达式。

var (
    home   = os.Getenv("HOME")
    user   = os.Getenv("USER")
    gopath = os.Getenv("GOPATH")
)

The init function  init 函数 ¶

Finally, each source file can define its own niladic init function to set up whatever state is required. (Actually each file can have multiple init functions.) And finally means finally: init is called after all the variable declarations in the package have evaluated their initializers, and those are evaluated only after all the imported packages have been initialized.
最后,每个源文件都可以定义自己的无参数 init 函数来设置所需的状态。(实际上每个文件可以有多个 init 函数。)而且 finally 就是 finally: init 会在包中所有变量声明的初始化器求值之后调用,而这些初始化器只有在所有导入的包都已初始化之后才会被求值。

Besides initializations that cannot be expressed as declarations, a common use of init functions is to verify or repair correctness of the program state before real execution begins.
除了无法用声明表达的初始化之外, init 函数的一个常见用途是在实际执行开始之前验证或修复程序状态的正确性。

func init() {
    if user == "" {
        log.Fatal("$USER not set")
    }
    if home == "" {
        home = "/home/" + user
    }
    if gopath == "" {
        gopath = home + "/go"
    }
    // gopath may be overridden by --gopath flag on command line.
    flag.StringVar(&gopath, "gopath", gopath, "override default GOPATH")
}

Methods  方法 ¶

Pointers vs. Values  指针与值 ¶

As we saw with ByteSize, methods can be defined for any named type (except a pointer or an interface); the receiver does not have to be a struct.
正如我们在 ByteSize 中看到的,方法可以为任何命名类型定义(指针或接口除外);接收者不必是结构体。

In the discussion of slices above, we wrote an Append function. We can define it as a method on slices instead. To do this, we first declare a named type to which we can bind the method, and then make the receiver for the method a value of that type.
在上面对切片的讨论中,我们编写了一个 Append 函数。我们可以将其定义为切片上的方法。为此,我们首先声明一个命名类型,以便绑定该方法,然后将该方法的接收者设为该类型的一个值。

type ByteSlice []byte

func (slice ByteSlice) Append(data []byte) []byte {
    // Body exactly the same as the Append function defined above.
}

This still requires the method to return the updated slice. We can eliminate that clumsiness by redefining the method to take a pointer to a ByteSlice as its receiver, so the method can overwrite the caller's slice.
这仍然要求方法返回更新后的切片。我们可以通过将方法重新定义为以 ByteSlice 的指针作为接收者来消除这种笨拙,这样方法就可以覆盖调用者的切片。

func (p *ByteSlice) Append(data []byte) {
    slice := *p
    // Body as above, without the return.
    *p = slice
}

In fact, we can do even better. If we modify our function so it looks like a standard Write method, like this,
事实上,我们可以做得更好。如果我们修改我们的函数,使其看起来像一个标准的 Write 方法,像这样,

func (p *ByteSlice) Write(data []byte) (n int, err error) {
    slice := *p
    // Again as above.
    *p = slice
    return len(data), nil
}

then the type *ByteSlice satisfies the standard interface io.Writer, which is handy. For instance, we can print into one.
那么类型 *ByteSlice 满足标准接口 io.Writer ,这很方便。例如,我们可以向其中打印。

    var b ByteSlice
    fmt.Fprintf(&b, "This hour has %d days\n", 7)

We pass the address of a ByteSlice because only *ByteSlice satisfies io.Writer. The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.
我们传递 ByteSlice 的地址,因为只有 *ByteSlice 满足 io.Writer 。关于接收者的指针与值的规则是,值方法可以在指针和值上调用,但指针方法只能在指针上调用。

This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake. There is a handy exception, though. When the value is addressable, the language takes care of the common case of invoking a pointer method on a value by inserting the address operator automatically. In our example, the variable b is addressable, so we can call its Write method with just b.Write. The compiler will rewrite that to (&b).Write for us.
这个规则产生的原因是指针方法可以修改接收者;在值上调用它们会导致方法接收到该值的副本,因此任何修改都会被丢弃。因此语言不允许这种错误。不过有一个方便的例外。当值是可寻址的时,语言会自动插入取地址操作符,以处理在值上调用指针方法的常见情况。在我们的例子中,变量 b 是可寻址的,所以我们可以仅用 b.Write 调用它的 Write 方法。编译器会帮我们将其重写为 (&b).Write

By the way, the idea of using Write on a slice of bytes is central to the implementation of bytes.Buffer.
顺便说一下,在字节切片上使用 Write 的想法是 bytes.Buffer 实现的核心。

Interfaces and other types
接口和其他类型 ¶

Interfaces  接口 ¶

Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here. We've seen a couple of simple examples already; custom printers can be implemented by a String method while Fprintf can generate output to anything with a Write method. Interfaces with only one or two methods are common in Go code, and are usually given a name derived from the method, such as io.Writer for something that implements Write.
Go 中的接口提供了一种指定对象行为的方式:如果某个东西能做这个,那么它就可以在这里使用。我们已经看到了一些简单的例子;自定义打印器可以通过一个 String 方法实现,而 Fprintf 可以生成输出到任何具有 Write 方法的对象。只有一两个方法的接口在 Go 代码中很常见,通常会根据方法的名称来命名,比如实现了 Write 的东西会被命名为 io.Writer

A type can implement multiple interfaces. For instance, a collection can be sorted by the routines in package sort if it implements sort.Interface, which contains Len(), Less(i, j int) bool, and Swap(i, j int), and it could also have a custom formatter. In this contrived example Sequence satisfies both.
一个类型可以实现多个接口。例如,如果一个集合实现了包含 Len()Less(i, j int) boolSwap(i, j int)sort.Interface 接口,那么它可以通过包 sort 中的例程进行排序,并且它还可以有一个自定义格式化器。在这个人为的例子中, Sequence 同时满足这两个接口。

type Sequence []int

// Methods required by sort.Interface.
func (s Sequence) Len() int {
    return len(s)
}
func (s Sequence) Less(i, j int) bool {
    return s[i] < s[j]
}
func (s Sequence) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

// Copy returns a copy of the Sequence.
func (s Sequence) Copy() Sequence {
    copy := make(Sequence, 0, len(s))
    return append(copy, s...)
}

// Method for printing - sorts the elements before printing.
func (s Sequence) String() string {
    s = s.Copy() // Make a copy; don't overwrite argument.
    sort.Sort(s)
    str := "["
    for i, elem := range s { // Loop is O(N²); will fix that in next example.
        if i > 0 {
            str += " "
        }
        str += fmt.Sprint(elem)
    }
    return str + "]"
}

Conversions  转换 ¶

The String method of Sequence is recreating the work that Sprint already does for slices. (It also has complexity O(N²), which is poor.) We can share the effort (and also speed it up) if we convert the Sequence to a plain []int before calling Sprint.
SequenceString 方法是在重新实现 Sprint 已经为切片完成的工作。(它的复杂度也是 O(N²),这很差。)如果我们在调用 Sprint 之前将 Sequence 转换为普通的 []int ,就可以共享工作量(并且加快速度)。

func (s Sequence) String() string {
    s = s.Copy()
    sort.Sort(s)
    return fmt.Sprint([]int(s))
}

This method is another example of the conversion technique for calling Sprintf safely from a String method. Because the two types (Sequence and []int) are the same if we ignore the type name, it's legal to convert between them. The conversion doesn't create a new value, it just temporarily acts as though the existing value has a new type. (There are other legal conversions, such as from integer to floating point, that do create a new value.)
这种方法是从 String 方法安全调用 Sprintf 的转换技术的另一个例子。因为这两种类型( Sequence[]int )如果忽略类型名称是相同的,所以在它们之间转换是合法的。转换不会创建一个新值,它只是暂时表现得好像现有值具有一个新类型。(还有其他合法的转换,比如从整数到浮点数,会创建一个新值。)

It's an idiom in Go programs to convert the type of an expression to access a different set of methods. As an example, we could use the existing type sort.IntSlice to reduce the entire example to this:
在 Go 程序中,将表达式的类型转换为访问不同的方法集是一种惯用法。例如,我们可以使用现有类型 sort.IntSlice 将整个示例简化为:

type Sequence []int

// Method for printing - sorts the elements before printing
func (s Sequence) String() string {
    s = s.Copy()
    sort.IntSlice(s).Sort()
    return fmt.Sprint([]int(s))
}

Now, instead of having Sequence implement multiple interfaces (sorting and printing), we're using the ability of a data item to be converted to multiple types (Sequence, sort.IntSlice and []int), each of which does some part of the job. That's more unusual in practice but can be effective.
现在,我们不是让 Sequence 实现多个接口(排序和打印),而是利用数据项能够转换为多种类型的能力( Sequencesort.IntSlice[]int ),每种类型完成部分工作。这在实际中较为少见,但可能很有效。

Interface conversions and type assertions
接口转换和类型断言 ¶

Type switches are a form of conversion: they take an interface and, for each case in the switch, in a sense convert it to the type of that case. Here's a simplified version of how the code under fmt.Printf turns a value into a string using a type switch. If it's already a string, we want the actual string value held by the interface, while if it has a String method we want the result of calling the method.
类型选择是一种转换形式:它接受一个接口,并且对于 switch 中的每个 case,从某种意义上将其转换为该 case 的类型。下面是 fmt.Printf 代码如何使用类型选择将值转换为字符串的简化版本。如果它已经是字符串,我们想要接口中实际持有的字符串值;如果它有一个 String 方法,我们想要调用该方法的结果。

type Stringer interface {
    String() string
}

var value interface{} // Value provided by caller.
switch str := value.(type) {
case string:
    return str
case Stringer:
    return str.String()
}

The first case finds a concrete value; the second converts the interface into another interface. It's perfectly fine to mix types this way.
第一个 case 找到一个具体值;第二个将接口转换为另一个接口。这样混合类型是完全可以的。

What if there's only one type we care about? If we know the value holds a string and we just want to extract it? A one-case type switch would do, but so would a type assertion. A type assertion takes an interface value and extracts from it a value of the specified explicit type. The syntax borrows from the clause opening a type switch, but with an explicit type rather than the type keyword:
如果我们只关心一种类型怎么办?如果我们知道值中包含一个 string ,并且我们只想提取它?单一情况的类型选择语句可以做到,但类型断言也可以。类型断言从接口值中提取指定显式类型的值。语法借用了类型选择语句开头的子句,但使用的是显式类型而不是 type 关键字:

value.(typeName)

and the result is a new value with the static type typeName. That type must either be the concrete type held by the interface, or a second interface type that the value can be converted to. To extract the string we know is in the value, we could write:
结果是一个具有静态类型 typeName 的新值。该类型必须是接口持有的具体类型,或者是值可以转换为的第二个接口类型。为了提取我们知道在值中的字符串,我们可以这样写:

str := value.(string)

But if it turns out that the value does not contain a string, the program will crash with a run-time error. To guard against that, use the "comma, ok" idiom to test, safely, whether the value is a string:
但如果结果值不包含字符串,程序将因运行时错误而崩溃。为防止这种情况,使用“逗号,ok”惯用法来安全地测试该值是否为字符串:

str, ok := value.(string)
if ok {
    fmt.Printf("string value is: %q\n", str)
} else {
    fmt.Printf("value is not a string\n")
}

If the type assertion fails, str will still exist and be of type string, but it will have the zero value, an empty string.
如果类型断言失败, str 仍然存在且类型为字符串,但它将具有零值,即空字符串。

As an illustration of the capability, here's an if-else statement that's equivalent to the type switch that opened this section.
作为功能的一个例子,这里有一个 if - else 语句,它等价于本节开头的类型选择语句。

if str, ok := value.(string); ok {
    return str
} else if str, ok := value.(Stringer); ok {
    return str.String()
}

Generality  通用性 ¶

If a type exists only to implement an interface and will never have exported methods beyond that interface, there is no need to export the type itself. Exporting just the interface makes it clear the value has no interesting behavior beyond what is described in the interface. It also avoids the need to repeat the documentation on every instance of a common method.
如果一个类型仅用于实现接口,并且不会有超出该接口的导出方法,则无需导出该类型本身。仅导出接口可以明确表示该值除了接口中描述的内容外没有其他有趣的行为。这也避免了在每个常见方法的实例上重复文档说明。

In such cases, the constructor should return an interface value rather than the implementing type. As an example, in the hash libraries both crc32.NewIEEE and adler32.New return the interface type hash.Hash32. Substituting the CRC-32 algorithm for Adler-32 in a Go program requires only changing the constructor call; the rest of the code is unaffected by the change of algorithm.
在这种情况下,构造函数应返回接口值而不是实现类型。例如,在哈希库中, crc32.NewIEEEadler32.New 都返回接口类型 hash.Hash32 。在 Go 程序中将 CRC-32 算法替换为 Adler-32 只需更改构造函数调用;其余代码不受算法更改的影响。

A similar approach allows the streaming cipher algorithms in the various crypto packages to be separated from the block ciphers they chain together. The Block interface in the crypto/cipher package specifies the behavior of a block cipher, which provides encryption of a single block of data. Then, by analogy with the bufio package, cipher packages that implement this interface can be used to construct streaming ciphers, represented by the Stream interface, without knowing the details of the block encryption.
类似的方法允许将各种 crypto 包中的流密码算法与它们链接在一起的分组密码分开。 crypto/cipher 包中的 Block 接口指定了分组密码的行为,该分组密码提供单个数据块的加密。然后,类比于 bufio 包,实现此接口的密码包可以用来构造流密码,由 Stream 接口表示,而无需了解分组加密的细节。

The crypto/cipher interfaces look like this:
crypto/cipher 接口看起来是这样的:

type Block interface {
    BlockSize() int
    Encrypt(dst, src []byte)
    Decrypt(dst, src []byte)
}

type Stream interface {
    XORKeyStream(dst, src []byte)
}

Here's the definition of the counter mode (CTR) stream, which turns a block cipher into a streaming cipher; notice that the block cipher's details are abstracted away:
以下是计数器模式(CTR)流的定义,它将块密码转换为流密码;请注意,块密码的细节被抽象化了:

// NewCTR returns a Stream that encrypts/decrypts using the given Block in
// counter mode. The length of iv must be the same as the Block's block size.
func NewCTR(block Block, iv []byte) Stream

NewCTR applies not just to one specific encryption algorithm and data source but to any implementation of the Block interface and any Stream. Because they return interface values, replacing CTR encryption with other encryption modes is a localized change. The constructor calls must be edited, but because the surrounding code must treat the result only as a Stream, it won't notice the difference.
NewCTR 不仅适用于某个特定的加密算法和数据源,而是适用于任何实现了 Block 接口和任何 Stream 的情况。因为它们返回接口值,用其他加密模式替换 CTR 加密是一个局部的更改。必须编辑构造函数调用,但由于周围的代码只将结果视为 Stream ,所以不会注意到差异。

Interfaces and methods  接口和方法 ¶

Since almost anything can have methods attached, almost anything can satisfy an interface. One illustrative example is in the http package, which defines the Handler interface. Any object that implements Handler can serve HTTP requests.
由于几乎任何东西都可以附加方法,几乎任何东西都可以满足接口。一个说明性的例子是在 http 包中,它定义了 Handler 接口。任何实现了 Handler 的对象都可以处理 HTTP 请求。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

ResponseWriter is itself an interface that provides access to the methods needed to return the response to the client. Those methods include the standard Write method, so an http.ResponseWriter can be used wherever an io.Writer can be used. Request is a struct containing a parsed representation of the request from the client.
ResponseWriter 本身是一个接口,提供访问返回客户端响应所需方法的途径。这些方法包括标准的 Write 方法,因此 http.ResponseWriter 可以在任何 io.Writer 可以使用的地方使用。 Request 是一个结构体,包含来自客户端请求的解析表示。

For brevity, let's ignore POSTs and assume HTTP requests are always GETs; that simplification does not affect the way the handlers are set up. Here's a trivial implementation of a handler to count the number of times the page is visited.
为了简洁起见,我们忽略 POST 请求,假设 HTTP 请求总是 GET 请求;这种简化不会影响处理程序的设置方式。下面是一个用于统计页面访问次数的简单处理程序实现。

// Simple counter server.
type Counter struct {
    n int
}

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ctr.n++
    fmt.Fprintf(w, "counter = %d\n", ctr.n)
}

(Keeping with our theme, note how Fprintf can print to an http.ResponseWriter.) In a real server, access to ctr.n would need protection from concurrent access. See the sync and atomic packages for suggestions.
(保持我们的主题,注意 Fprintf 如何打印到 http.ResponseWriter 。)在真实的服务器中,访问 ctr.n 需要防止并发访问。有关建议,请参见 syncatomic 包。

For reference, here's how to attach such a server to a node on the URL tree.
作为参考,以下是如何将这样的服务器附加到 URL 树上的节点。

import "net/http"
...
ctr := new(Counter)
http.Handle("/counter", ctr)

But why make Counter a struct? An integer is all that's needed. (The receiver needs to be a pointer so the increment is visible to the caller.)
但是为什么要把 Counter 设为结构体?只需要一个整数即可。(接收者需要是指针,这样递增才能对调用者可见。)

// Simpler counter server.
type Counter int

func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    *ctr++
    fmt.Fprintf(w, "counter = %d\n", *ctr)
}

What if your program has some internal state that needs to be notified that a page has been visited? Tie a channel to the web page.
如果你的程序有一些内部状态需要被通知某个页面已被访问,该怎么办?将一个通道绑定到网页。

// A channel that sends a notification on each visit.
// (Probably want the channel to be buffered.)
type Chan chan *http.Request

func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ch <- req
    fmt.Fprint(w, "notification sent")
}

Finally, let's say we wanted to present on /args the arguments used when invoking the server binary. It's easy to write a function to print the arguments.
最后,假设我们想展示调用服务器二进制文件时使用的参数。编写一个打印参数的函数很简单。

func ArgServer() {
    fmt.Println(os.Args)
}

How do we turn that into an HTTP server? We could make ArgServer a method of some type whose value we ignore, but there's a cleaner way. Since we can define a method for any type except pointers and interfaces, we can write a method for a function. The http package contains this code:
我们如何将其变成一个 HTTP 服务器?我们可以将 ArgServer 作为某个类型的方法,其值被忽略,但有一种更简洁的方式。由于我们可以为除指针和接口之外的任何类型定义方法,我们可以为函数编写方法。 http 包含以下代码:

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers.  If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, req).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, req *Request) {
    f(w, req)
}

HandlerFunc is a type with a method, ServeHTTP, so values of that type can serve HTTP requests. Look at the implementation of the method: the receiver is a function, f, and the method calls f. That may seem odd but it's not that different from, say, the receiver being a channel and the method sending on the channel.
HandlerFunc 是一个带有方法 ServeHTTP 的类型,因此该类型的值可以处理 HTTP 请求。看看该方法的实现:接收者是一个函数 f ,该方法调用了 f 。这看起来可能有些奇怪,但其实和接收者是一个通道并在该方法中向通道发送数据并没有太大区别。

To make ArgServer into an HTTP server, we first modify it to have the right signature.
要将 ArgServer 变成一个 HTTP 服务器,我们首先修改它以具有正确的签名。

// Argument server.
func ArgServer(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, os.Args)
}

ArgServer now has the same signature as HandlerFunc, so it can be converted to that type to access its methods, just as we converted Sequence to IntSlice to access IntSlice.Sort. The code to set it up is concise:
ArgServer 现在具有与 HandlerFunc 相同的签名,因此可以转换为该类型以访问其方法,就像我们将 Sequence 转换为 IntSlice 以访问 IntSlice.Sort 一样。设置它的代码很简洁:

http.Handle("/args", http.HandlerFunc(ArgServer))

When someone visits the page /args, the handler installed at that page has value ArgServer and type HandlerFunc. The HTTP server will invoke the method ServeHTTP of that type, with ArgServer as the receiver, which will in turn call ArgServer (via the invocation f(w, req) inside HandlerFunc.ServeHTTP). The arguments will then be displayed.
当有人访问页面 /args 时,该页面安装的处理程序的值为 ArgServer ,类型为 HandlerFunc 。HTTP 服务器将调用该类型的 ServeHTTP 方法,以 ArgServer 作为接收者,进而调用 ArgServer (通过 HandlerFunc.ServeHTTP 内的调用 f(w, req) )。然后将显示参数。

In this section we have made an HTTP server from a struct, an integer, a channel, and a function, all because interfaces are just sets of methods, which can be defined for (almost) any type.
在本节中,我们从一个结构体、一个整数、一个通道和一个函数创建了一个 HTTP 服务器,这都是因为接口只是方法的集合,而方法几乎可以为任何类型定义。

The blank identifier  空白标识符 ¶

We've mentioned the blank identifier a couple of times now, in the context of for range loops and maps. The blank identifier can be assigned or declared with any value of any type, with the value discarded harmlessly. It's a bit like writing to the Unix /dev/null file: it represents a write-only value to be used as a place-holder where a variable is needed but the actual value is irrelevant. It has uses beyond those we've seen already.
我们已经在 for range 循环和映射的上下文中提到了空白标识符几次。空白标识符可以被赋值或声明为任何类型的任何值,且该值会被安全地丢弃。这有点像写入 Unix 的 /dev/null 文件:它表示一个只写值,用作需要变量但实际值无关紧要的占位符。它的用途不仅限于我们已经看到的那些。

The blank identifier in multiple assignment
多重赋值中的空白标识符 ¶

The use of a blank identifier in a for range loop is a special case of a general situation: multiple assignment.
for range 循环中使用空白标识符是一个特殊情况,属于多重赋值的一般情况。

If an assignment requires multiple values on the left side, but one of the values will not be used by the program, a blank identifier on the left-hand-side of the assignment avoids the need to create a dummy variable and makes it clear that the value is to be discarded. For instance, when calling a function that returns a value and an error, but only the error is important, use the blank identifier to discard the irrelevant value.
如果赋值语句左侧需要多个值,但其中一个值不会被程序使用,那么在赋值语句左侧使用空白标识符可以避免创建一个无用变量,并且明确表示该值将被丢弃。例如,当调用一个返回值和错误的函数时,如果只关心错误,可以使用空白标识符来丢弃无关的值。

if _, err := os.Stat(path); os.IsNotExist(err) {
    fmt.Printf("%s does not exist\n", path)
}

Occasionally you'll see code that discards the error value in order to ignore the error; this is terrible practice. Always check error returns; they're provided for a reason.
有时你会看到代码丢弃错误值以忽略错误;这是非常糟糕的做法。一定要检查错误返回值;它们的存在是有原因的。

// Bad! This code will crash if path does not exist.
fi, _ := os.Stat(path)
if fi.IsDir() {
    fmt.Printf("%s is a directory\n", path)
}

Unused imports and variables
未使用的导入和变量 ¶

It is an error to import a package or to declare a variable without using it. Unused imports bloat the program and slow compilation, while a variable that is initialized but not used is at least a wasted computation and perhaps indicative of a larger bug. When a program is under active development, however, unused imports and variables often arise and it can be annoying to delete them just to have the compilation proceed, only to have them be needed again later. The blank identifier provides a workaround.
导入包或声明变量却不使用是错误的。未使用的导入会使程序臃肿并且减慢编译速度,而初始化但未使用的变量至少是浪费计算资源,甚至可能表明存在更大的错误。然而,当程序处于积极开发阶段时,未使用的导入和变量经常出现,删除它们以使编译继续进行可能会很烦人,因为它们以后可能还会被需要。空白标识符提供了一种解决方法。

This half-written program has two unused imports (fmt and io) and an unused variable (fd), so it will not compile, but it would be nice to see if the code so far is correct.
这个半成品程序有两个未使用的导入( fmtio )和一个未使用的变量( fd ),因此它无法编译,但查看目前的代码是否正确会很有帮助。

package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

func main() {
    fd, err := os.Open("test.go")
    if err != nil {
        log.Fatal(err)
    }
    // TODO: use fd.
}

To silence complaints about the unused imports, use a blank identifier to refer to a symbol from the imported package. Similarly, assigning the unused variable fd to the blank identifier will silence the unused variable error. This version of the program does compile.
为了消除关于未使用导入的抱怨,可以使用空白标识符来引用导入包中的符号。同样,将未使用的变量 fd 赋值给空白标识符可以消除未使用变量的错误。这个版本的程序可以编译通过。

package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

var _ = fmt.Printf // For debugging; delete when done.
var _ io.Reader    // For debugging; delete when done.

func main() {
    fd, err := os.Open("test.go")
    if err != nil {
        log.Fatal(err)
    }
    // TODO: use fd.
    _ = fd
}

By convention, the global declarations to silence import errors should come right after the imports and be commented, both to make them easy to find and as a reminder to clean things up later.
按照惯例,用于消除导入错误的全局声明应紧跟在导入语句之后,并加以注释,既便于查找,也提醒以后清理。

Import for side effect
仅为副作用导入 ¶

An unused import like fmt or io in the previous example should eventually be used or removed: blank assignments identify code as a work in progress. But sometimes it is useful to import a package only for its side effects, without any explicit use. For example, during its init function, the net/http/pprof package registers HTTP handlers that provide debugging information. It has an exported API, but most clients need only the handler registration and access the data through a web page. To import the package only for its side effects, rename the package to the blank identifier:
像前面示例中的 fmtio 这样的未使用导入最终应该被使用或移除:空白赋值标识代码仍在进行中。但有时仅为了副作用而导入包是有用的,而不需要任何显式使用。例如,在其 init 函数期间, net/http/pprof 包注册了提供调试信息的 HTTP 处理程序。它有一个导出的 API,但大多数客户端只需要处理程序注册,并通过网页访问数据。为了仅为了副作用导入该包,可以将包重命名为空白标识符:

import _ "net/http/pprof"

This form of import makes clear that the package is being imported for its side effects, because there is no other possible use of the package: in this file, it doesn't have a name. (If it did, and we didn't use that name, the compiler would reject the program.)
这种导入形式明确表示该包是为了其副作用而导入的,因为该包在此文件中没有其他可能的用途:(如果有名字且我们未使用该名字,编译器会拒绝该程序。)

Interface checks  接口检查 ¶

As we saw in the discussion of interfaces above, a type need not declare explicitly that it implements an interface. Instead, a type implements the interface just by implementing the interface's methods. In practice, most interface conversions are static and therefore checked at compile time. For example, passing an *os.File to a function expecting an io.Reader will not compile unless *os.File implements the io.Reader interface.
正如我们在上文关于接口的讨论中看到的,一个类型不需要显式声明它实现了某个接口。相反,一个类型只需实现该接口的方法即可实现该接口。在实际操作中,大多数接口转换是静态的,因此在编译时进行检查。例如,将一个 *os.File 传递给期望 io.Reader 的函数时,除非 *os.File 实现了 io.Reader 接口,否则代码无法编译。

Some interface checks do happen at run-time, though. One instance is in the encoding/json package, which defines a Marshaler interface. When the JSON encoder receives a value that implements that interface, the encoder invokes the value's marshaling method to convert it to JSON instead of doing the standard conversion. The encoder checks this property at run time with a type assertion like:
某些接口检查确实会在运行时发生。一个例子是在 encoding/json 包中,该包定义了一个 Marshaler 接口。当 JSON 编码器接收到实现该接口的值时,编码器会调用该值的封送方法将其转换为 JSON,而不是进行标准转换。编码器通过类似以下的类型断言在运行时检查此属性:

m, ok := val.(json.Marshaler)

If it's necessary only to ask whether a type implements an interface, without actually using the interface itself, perhaps as part of an error check, use the blank identifier to ignore the type-asserted value:
如果仅仅需要询问某个类型是否实现了某个接口,而不实际使用该接口本身,可能作为错误检查的一部分,可以使用空白标识符来忽略类型断言的值:

if _, ok := val.(json.Marshaler); ok {
    fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
}

One place this situation arises is when it is necessary to guarantee within the package implementing the type that it actually satisfies the interface. If a type—for example, json.RawMessage—needs a custom JSON representation, it should implement json.Marshaler, but there are no static conversions that would cause the compiler to verify this automatically. If the type inadvertently fails to satisfy the interface, the JSON encoder will still work, but will not use the custom implementation. To guarantee that the implementation is correct, a global declaration using the blank identifier can be used in the package:
这种情况出现的一个地方是在实现该类型的包内必须保证它实际上满足接口的情况下。如果一个类型——例如 json.RawMessage ——需要自定义 JSON 表示,它应该实现 json.Marshaler ,但没有静态转换会导致编译器自动验证这一点。如果该类型无意中未能满足接口,JSON 编码器仍然会工作,但不会使用自定义实现。为了保证实现的正确性,可以在包中使用空白标识符进行全局声明:

var _ json.Marshaler = (*RawMessage)(nil)

In this declaration, the assignment involving a conversion of a *RawMessage to a Marshaler requires that *RawMessage implements Marshaler, and that property will be checked at compile time. Should the json.Marshaler interface change, this package will no longer compile and we will be on notice that it needs to be updated.
在此声明中,涉及将 *RawMessage 转换为 Marshaler 的赋值要求 *RawMessage 实现 Marshaler ,该属性将在编译时进行检查。如果 json.Marshaler 接口发生变化,该包将无法编译,我们将收到需要更新的通知。

The appearance of the blank identifier in this construct indicates that the declaration exists only for the type checking, not to create a variable. Don't do this for every type that satisfies an interface, though. By convention, such declarations are only used when there are no static conversions already present in the code, which is a rare event.
在此结构中出现空白标识符表示声明仅用于类型检查,而不是创建变量。不过,不要对每个满足接口的类型都这样做。按照惯例,只有在代码中没有静态转换存在时,才使用此类声明,这种情况很少见。

Embedding  嵌入 ¶

Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.
Go 不提供典型的、基于类型的子类概念,但它确实具有通过在结构体或接口中嵌入类型来“借用”实现部分功能的能力。

Interface embedding is very simple. We've mentioned the io.Reader and io.Writer interfaces before; here are their definitions.
接口嵌入非常简单。我们之前提到过 io.Readerio.Writer 接口;这里是它们的定义。

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

The io package also exports several other interfaces that specify objects that can implement several such methods. For instance, there is io.ReadWriter, an interface containing both Read and Write. We could specify io.ReadWriter by listing the two methods explicitly, but it's easier and more evocative to embed the two interfaces to form the new one, like this:
io 包还导出几个其他接口,这些接口指定了可以实现多个此类方法的对象。例如,有 io.ReadWriter ,这是一个包含 ReadWrite 的接口。我们可以通过显式列出这两个方法来指定 io.ReadWriter ,但嵌入这两个接口以形成新的接口更简单且更具表现力,像这样:

// ReadWriter is the interface that combines the Reader and Writer interfaces.
type ReadWriter interface {
    Reader
    Writer
}

This says just what it looks like: A ReadWriter can do what a Reader does and what a Writer does; it is a union of the embedded interfaces. Only interfaces can be embedded within interfaces.
这句话的意思就是:一个 ReadWriter 可以做一个 Reader 做的事情,也可以做一个 Writer 做的事情;它是嵌入接口的联合。只有接口可以嵌入到接口中。

The same basic idea applies to structs, but with more far-reaching implications. The bufio package has two struct types, bufio.Reader and bufio.Writer, each of which of course implements the analogous interfaces from package io. And bufio also implements a buffered reader/writer, which it does by combining a reader and a writer into one struct using embedding: it lists the types within the struct but does not give them field names.
相同的基本思想也适用于结构体,但影响更为深远。 bufio 包有两个结构体类型, bufio.Readerbufio.Writer ,它们当然都实现了来自 io 包的类似接口。 bufio 还实现了一个缓冲读写器,它通过将读写器组合成一个结构体来实现嵌入:在结构体中列出类型但不为它们指定字段名。

// ReadWriter stores pointers to a Reader and a Writer.
// It implements io.ReadWriter.
type ReadWriter struct {
    *Reader  // *bufio.Reader
    *Writer  // *bufio.Writer
}

The embedded elements are pointers to structs and of course must be initialized to point to valid structs before they can be used. The ReadWriter struct could be written as
嵌入的元素是指向结构体的指针,当然必须先初始化为指向有效的结构体,才能使用它们。 ReadWriter 结构体可以写成

type ReadWriter struct {
    reader *Reader
    writer *Writer
}

but then to promote the methods of the fields and to satisfy the io interfaces, we would also need to provide forwarding methods, like this:
但为了提升字段的方法并满足 io 接口,我们还需要提供转发方法,如下所示:

func (rw *ReadWriter) Read(p []byte) (n int, err error) {
    return rw.reader.Read(p)
}

By embedding the structs directly, we avoid this bookkeeping. The methods of embedded types come along for free, which means that bufio.ReadWriter not only has the methods of bufio.Reader and bufio.Writer, it also satisfies all three interfaces: io.Reader, io.Writer, and io.ReadWriter.
通过直接嵌入结构体,我们避免了这种记录。嵌入类型的方法是自动继承的,这意味着 bufio.ReadWriter 不仅拥有 bufio.Readerbufio.Writer 的方法,还满足 io.Readerio.Writerio.ReadWriter 这三个接口。

There's an important way in which embedding differs from subclassing. When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one. In our example, when the Read method of a bufio.ReadWriter is invoked, it has exactly the same effect as the forwarding method written out above; the receiver is the reader field of the ReadWriter, not the ReadWriter itself.
嵌入与子类化有一个重要的区别。当我们嵌入一个类型时,该类型的方法会成为外部类型的方法,但当这些方法被调用时,方法的接收者是内部类型,而不是外部类型。在我们的例子中,当调用 Readbufio.ReadWriter 方法时,其效果与上面写出的转发方法完全相同;接收者是 ReadWriterreader 字段,而不是 ReadWriter 本身。

Embedding can also be a simple convenience. This example shows an embedded field alongside a regular, named field.
嵌入也可以是一种简单的便利。这个例子展示了一个嵌入字段和一个普通的命名字段并列存在。

type Job struct {
    Command string
    *log.Logger
}

The Job type now has the Print, Printf, Println and other methods of *log.Logger. We could have given the Logger a field name, of course, but it's not necessary to do so. And now, once initialized, we can log to the Job:
Job 类型现在具有 PrintPrintfPrintln 以及 *log.Logger 的其他方法。当然,我们本可以给 Logger 一个字段名,但这并非必要。现在,一旦初始化,我们就可以记录到 Job

job.Println("starting now...")

The Logger is a regular field of the Job struct, so we can initialize it in the usual way inside the constructor for Job, like this,
LoggerJob 结构体的一个普通字段,因此我们可以像通常那样在 Job 的构造函数中初始化它,像这样,

func NewJob(command string, logger *log.Logger) *Job {
    return &Job{command, logger}
}

or with a composite literal,
或使用复合字面量,

job := &Job{command, log.New(os.Stderr, "Job: ", log.Ldate)}

If we need to refer to an embedded field directly, the type name of the field, ignoring the package qualifier, serves as a field name, as it did in the Read method of our ReadWriter struct. Here, if we needed to access the *log.Logger of a Job variable job, we would write job.Logger, which would be useful if we wanted to refine the methods of Logger.
如果我们需要直接引用嵌入字段,字段的类型名(忽略包限定符)作为字段名,就像我们 ReadWriter 结构体的 Read 方法中那样。在这里,如果我们需要访问 Job 变量 job*log.Logger ,我们会写成 job.Logger ,这在我们想要细化 Logger 的方法时会很有用。

func (job *Job) Printf(format string, args ...interface{}) {
    job.Logger.Printf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}

Embedding types introduces the problem of name conflicts but the rules to resolve them are simple. First, a field or method X hides any other item X in a more deeply nested part of the type. If log.Logger contained a field or method called Command, the Command field of Job would dominate it.
嵌入类型引入了名称冲突的问题,但解决它们的规则很简单。首先,一个字段或方法 X 会隐藏类型中更深层嵌套部分的任何其他项目 X 。如果 log.Logger 包含一个名为 Command 的字段或方法, JobCommand 字段将优先于它。

Second, if the same name appears at the same nesting level, it is usually an error; it would be erroneous to embed log.Logger if the Job struct contained another field or method called Logger. However, if the duplicate name is never mentioned in the program outside the type definition, it is OK. This qualification provides some protection against changes made to types embedded from outside; there is no problem if a field is added that conflicts with another field in another subtype if neither field is ever used.
其次,如果同一个名称出现在相同的嵌套层级,通常是错误的;如果 Job 结构体包含另一个名为 Logger 的字段或方法,嵌入 log.Logger 就是错误的。然而,如果重复的名称在类型定义之外的程序中从未被提及,则是可以的。这种限定对从外部嵌入的类型所做的更改提供了一定的保护;如果添加了一个字段与另一个子类型中的字段冲突,但两个字段都未被使用,则没有问题。

Concurrency  并发 ¶

Share by communicating  通过通信共享 ¶

Concurrent programming is a large topic and there is space only for some Go-specific highlights here.
并发编程是一个庞大的主题,这里只能介绍一些 Go 语言特有的重点内容。

Concurrent programming in many environments is made difficult by the subtleties required to implement correct access to shared variables. Go encourages a different approach in which shared values are passed around on channels and, in fact, never actively shared by separate threads of execution. Only one goroutine has access to the value at any given time. Data races cannot occur, by design. To encourage this way of thinking we have reduced it to a slogan:
在许多环境中,并发编程因实现对共享变量的正确访问所需的细微差别而变得困难。Go 鼓励一种不同的方法,即通过通道传递共享值,实际上从不被不同执行线程主动共享。任何时刻只有一个 goroutine 可以访问该值。设计上无法发生数据竞争。为了鼓励这种思维方式,我们将其简化为一句口号:

Do not communicate by sharing memory; instead, share memory by communicating.
不要通过共享内存来通信;相反,要通过通信来共享内存。

This approach can be taken too far. Reference counts may be best done by putting a mutex around an integer variable, for instance. But as a high-level approach, using channels to control access makes it easier to write clear, correct programs.
这种方法也可能被过度使用。例如,引用计数最好是通过在整数变量周围加一个互斥锁来完成。但作为一种高级方法,使用通道来控制访问使得编写清晰、正确的程序更容易。

One way to think about this model is to consider a typical single-threaded program running on one CPU. It has no need for synchronization primitives. Now run another such instance; it too needs no synchronization. Now let those two communicate; if the communication is the synchronizer, there's still no need for other synchronization. Unix pipelines, for example, fit this model perfectly. Although Go's approach to concurrency originates in Hoare's Communicating Sequential Processes (CSP), it can also be seen as a type-safe generalization of Unix pipes.
思考这种模型的一种方式是考虑一个典型的单线程程序在一个 CPU 上运行。它不需要同步原语。现在再运行另一个这样的实例;它同样不需要同步。现在让这两个实例通信;如果通信本身就是同步器,那么就不需要其他同步机制了。例如,Unix 管道完美地符合这个模型。虽然 Go 的并发方法起源于 Hoare 的通信顺序进程(CSP),但它也可以被看作是 Unix 管道的类型安全泛化。

Goroutines

They're called goroutines because the existing terms—threads, coroutines, processes, and so on—convey inaccurate connotations. A goroutine has a simple model: it is a function executing concurrently with other goroutines in the same address space. It is lightweight, costing little more than the allocation of stack space. And the stacks start small, so they are cheap, and grow by allocating (and freeing) heap storage as required.
它们被称为 goroutine 是因为现有的术语——线程、协程、进程等等——传达了不准确的含义。goroutine 有一个简单的模型:它是在同一地址空间中与其他 goroutine 并发执行的函数。它非常轻量,开销仅仅是分配栈空间。栈起始很小,因此成本低,并且会根据需要通过分配(和释放)堆存储来增长。

Goroutines are multiplexed onto multiple OS threads so if one should block, such as while waiting for I/O, others continue to run. Their design hides many of the complexities of thread creation and management.
Goroutine 被多路复用到多个操作系统线程上,因此如果其中一个阻塞,比如等待 I/O,其他的仍然可以继续运行。它们的设计隐藏了许多线程创建和管理的复杂性。

Prefix a function or method call with the go keyword to run the call in a new goroutine. When the call completes, the goroutine exits, silently. (The effect is similar to the Unix shell's & notation for running a command in the background.)
在函数或方法调用前加上 go 关键字,可以让该调用在一个新的 goroutine 中运行。当调用完成时,goroutine 会静默退出。(这个效果类似于 Unix shell 中用 & 表示在后台运行命令的方式。)

go list.Sort()  // run list.Sort concurrently; don't wait for it.

A function literal can be handy in a goroutine invocation.
在调用 goroutine 时,函数字面量非常方便。

func Announce(message string, delay time.Duration) {
    go func() {
        time.Sleep(delay)
        fmt.Println(message)
    }()  // Note the parentheses - must call the function.
}

In Go, function literals are closures: the implementation makes sure the variables referred to by the function survive as long as they are active.
在 Go 语言中,函数字面量是闭包:实现确保函数引用的变量在它们处于活动状态时一直存在。

These examples aren't too practical because the functions have no way of signaling completion. For that, we need channels.
这些示例不太实用,因为函数无法发出完成的信号。为此,我们需要通道。

Channels  通道 ¶

Like maps, channels are allocated with make, and the resulting value acts as a reference to an underlying data structure. If an optional integer parameter is provided, it sets the buffer size for the channel. The default is zero, for an unbuffered or synchronous channel.
像映射一样,通道是通过 make 分配的,生成的值作为对底层数据结构的引用。如果提供了一个可选的整数参数,则该参数设置通道的缓冲区大小。默认值为零,表示无缓冲或同步通道。

ci := make(chan int)            // unbuffered channel of integers
cj := make(chan int, 0)         // unbuffered channel of integers
cs := make(chan *os.File, 100)  // buffered channel of pointers to Files

Unbuffered channels combine communication—the exchange of a value—with synchronization—guaranteeing that two calculations (goroutines) are in a known state.
无缓冲通道将通信——值的交换——与同步——保证两个计算(goroutine)处于已知状态——结合起来。

There are lots of nice idioms using channels. Here's one to get us started. In the previous section we launched a sort in the background. A channel can allow the launching goroutine to wait for the sort to complete.
有很多使用通道的好习语。这里有一个让我们开始的例子。在上一节中,我们在后台启动了一个排序。通道可以让启动的 goroutine 等待排序完成。

c := make(chan int)  // Allocate a channel.
// Start the sort in a goroutine; when it completes, signal on the channel.
go func() {
    list.Sort()
    c <- 1  // Send a signal; value does not matter.
}()
doSomethingForAWhile()
<-c   // Wait for sort to finish; discard sent value.

Receivers always block until there is data to receive. If the channel is unbuffered, the sender blocks until the receiver has received the value. If the channel has a buffer, the sender blocks only until the value has been copied to the buffer; if the buffer is full, this means waiting until some receiver has retrieved a value.
接收者总是阻塞,直到有数据可接收。如果通道是无缓冲的,发送者会阻塞,直到接收者接收了该值。如果通道有缓冲区,发送者只会阻塞,直到该值被复制到缓冲区;如果缓冲区已满,则意味着需要等待某个接收者取走一个值。

A buffered channel can be used like a semaphore, for instance to limit throughput. In this example, incoming requests are passed to handle, which sends a value into the channel, processes the request, and then receives a value from the channel to ready the “semaphore” for the next consumer. The capacity of the channel buffer limits the number of simultaneous calls to process.
缓冲通道可以用作信号量,例如限制吞吐量。在此示例中,传入的请求被传递给 handle ,它向通道发送一个值,处理请求,然后从通道接收一个值,为下一个消费者准备“信号量”。通道缓冲区的容量限制了对 process 的同时调用数量。

var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
    sem <- 1    // Wait for active queue to drain.
    process(r)  // May take a long time.
    <-sem       // Done; enable next request to run.
}

func Serve(queue chan *Request) {
    for {
        req := <-queue
        go handle(req)  // Don't wait for handle to finish.
    }
}

Once MaxOutstanding handlers are executing process, any more will block trying to send into the filled channel buffer, until one of the existing handlers finishes and receives from the buffer.
一旦 MaxOutstanding 处理程序正在执行 process ,任何更多的处理程序在尝试发送到已满的通道缓冲区时都会被阻塞,直到其中一个现有的处理程序完成并从缓冲区接收。

This design has a problem, though: Serve creates a new goroutine for every incoming request, even though only MaxOutstanding of them can run at any moment. As a result, the program can consume unlimited resources if the requests come in too fast. We can address that deficiency by changing Serve to gate the creation of the goroutines:
这个设计有一个问题: Serve 为每个传入请求创建一个新的 goroutine,尽管在任何时刻只有 MaxOutstanding 个能够运行。因此,如果请求来得太快,程序可能会消耗无限的资源。我们可以通过更改 Serve 来控制 goroutine 的创建,从而解决这个缺陷:

func Serve(queue chan *Request) {
    for req := range queue {
        sem <- 1
        go func() {
            process(req)
            <-sem
        }()
    }
}

(Note that in Go versions before 1.22 this code has a bug: the loop variable is shared across all goroutines. See the Go wiki for details.)
(请注意,在 Go 1.22 之前的版本中,这段代码存在一个错误:循环变量在所有 goroutine 之间共享。详情请参见 Go 维基。)

Another approach that manages resources well is to start a fixed number of handle goroutines all reading from the request channel. The number of goroutines limits the number of simultaneous calls to process. This Serve function also accepts a channel on which it will be told to exit; after launching the goroutines it blocks receiving from that channel.
另一种有效管理资源的方法是启动固定数量的 handle goroutines,全部从请求通道读取。goroutines 的数量限制了对 process 的同时调用数。这个 Serve 函数还接受一个通道,用于接收退出指令;在启动 goroutines 后,它会阻塞等待该通道的消息。

func handle(queue chan *Request) {
    for r := range queue {
        process(r)
    }
}

func Serve(clientRequests chan *Request, quit chan bool) {
    // Start handlers
    for i := 0; i < MaxOutstanding; i++ {
        go handle(clientRequests)
    }
    <-quit  // Wait to be told to exit.
}

Channels of channels  通道的通道 ¶

One of the most important properties of Go is that a channel is a first-class value that can be allocated and passed around like any other. A common use of this property is to implement safe, parallel demultiplexing.
Go 最重要的特性之一是通道是一等公民,可以像其他值一样分配和传递。这个特性的一个常见用途是实现安全的并行多路复用。

In the example in the previous section, handle was an idealized handler for a request but we didn't define the type it was handling. If that type includes a channel on which to reply, each client can provide its own path for the answer. Here's a schematic definition of type Request.
在上一节的示例中, handle 是一个理想化的请求处理器,但我们没有定义它所处理的类型。如果该类型包含一个用于回复的通道,每个客户端都可以提供自己的答案路径。下面是类型 Request 的示意定义。

type Request struct {
    args        []int
    f           func([]int) int
    resultChan  chan int
}

The client provides a function and its arguments, as well as a channel inside the request object on which to receive the answer.
客户端提供一个函数及其参数,以及请求对象中用于接收答案的通道。

func sum(a []int) (s int) {
    for _, v := range a {
        s += v
    }
    return
}

request := &Request{[]int{3, 4, 5}, sum, make(chan int)}
// Send request
clientRequests <- request
// Wait for response.
fmt.Printf("answer: %d\n", <-request.resultChan)

On the server side, the handler function is the only thing that changes.
在服务器端,唯一变化的是处理函数。

func handle(queue chan *Request) {
    for req := range queue {
        req.resultChan <- req.f(req.args)
    }
}

There's clearly a lot more to do to make it realistic, but this code is a framework for a rate-limited, parallel, non-blocking RPC system, and there's not a mutex in sight.
显然,要使其更现实还有很多工作要做,但这段代码是一个限速、并行、非阻塞 RPC 系统的框架,而且完全没有使用互斥锁。

Parallelization  并行化 ¶

Another application of these ideas is to parallelize a calculation across multiple CPU cores. If the calculation can be broken into separate pieces that can execute independently, it can be parallelized, with a channel to signal when each piece completes.
这些思想的另一个应用是将计算分布到多个 CPU 核心上并行执行。如果计算可以拆分成可以独立执行的多个部分,就可以并行化,并使用通道来在每个部分完成时发出信号。

Let's say we have an expensive operation to perform on a vector of items, and that the value of the operation on each item is independent, as in this idealized example.
假设我们需要对一组项目执行一个耗时的操作,并且每个项目的操作值是相互独立的,就像这个理想化的例子一样。

type Vector []float64

// Apply the operation to v[i], v[i+1] ... up to v[n-1].
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
    for ; i < n; i++ {
        v[i] += u.Op(v[i])
    }
    c <- 1    // signal that this piece is done
}

We launch the pieces independently in a loop, one per CPU. They can complete in any order but it doesn't matter; we just count the completion signals by draining the channel after launching all the goroutines.
我们在循环中独立启动这些部分,每个 CPU 启动一个。它们可以按任意顺序完成,但这无关紧要;我们只需在启动所有 goroutine 后通过读取通道来统计完成信号。

const numCPU = 4 // number of CPU cores

func (v Vector) DoAll(u Vector) {
    c := make(chan int, numCPU)  // Buffering optional but sensible.
    for i := 0; i < numCPU; i++ {
        go v.DoSome(i*len(v)/numCPU, (i+1)*len(v)/numCPU, u, c)
    }
    // Drain the channel.
    for i := 0; i < numCPU; i++ {
        <-c    // wait for one task to complete
    }
    // All done.
}

Rather than create a constant value for numCPU, we can ask the runtime what value is appropriate. The function runtime.NumCPU returns the number of hardware CPU cores in the machine, so we could write
与其为 numCPU 创建一个常量值,不如询问运行时哪个值是合适的。函数 runtime.NumCPU 返回机器中硬件 CPU 核心的数量,因此我们可以写成

var numCPU = runtime.NumCPU()

There is also a function runtime.GOMAXPROCS, which reports (or sets) the user-specified number of cores that a Go program can have running simultaneously. It defaults to the value of runtime.NumCPU but can be overridden by setting the similarly named shell environment variable or by calling the function with a positive number. Calling it with zero just queries the value. Therefore if we want to honor the user's resource request, we should write
还有一个函数 runtime.GOMAXPROCS ,它报告(或设置)Go 程序可以同时运行的用户指定核心数。它默认为 runtime.NumCPU 的值,但可以通过设置同名的 shell 环境变量或调用该函数并传入正数来覆盖。传入零则只是查询该值。因此,如果我们想尊重用户的资源请求,应该这样写

var numCPU = runtime.GOMAXPROCS(0)

Be sure not to confuse the ideas of concurrency—structuring a program as independently executing components—and parallelism—executing calculations in parallel for efficiency on multiple CPUs. Although the concurrency features of Go can make some problems easy to structure as parallel computations, Go is a concurrent language, not a parallel one, and not all parallelization problems fit Go's model. For a discussion of the distinction, see the talk cited in this blog post.
一定不要混淆并发的概念——将程序结构化为独立执行的组件——和并行——为了在多 CPU 上提高效率而并行执行计算。虽然 Go 的并发特性可以使某些问题容易结构化为并行计算,但 Go 是一门并发语言,而非并行语言,并非所有并行化问题都适合 Go 的模型。关于两者区别的讨论,请参见这篇博客文章中引用的演讲。

A leaky buffer  一个泄漏缓冲区 ¶

The tools of concurrent programming can even make non-concurrent ideas easier to express. Here's an example abstracted from an RPC package. The client goroutine loops receiving data from some source, perhaps a network. To avoid allocating and freeing buffers, it keeps a free list, and uses a buffered channel to represent it. If the channel is empty, a new buffer gets allocated. Once the message buffer is ready, it's sent to the server on serverChan.
并发编程的工具甚至可以使非并发的想法更容易表达。这里有一个从 RPC 包中抽象出来的例子。客户端 goroutine 循环接收来自某个来源的数据,可能是网络。为了避免分配和释放缓冲区,它保持一个空闲列表,并使用带缓冲的通道来表示它。如果通道为空,则分配一个新的缓冲区。一旦消息缓冲区准备好,就通过 serverChan 发送给服务器。

var freeList = make(chan *Buffer, 100)
var serverChan = make(chan *Buffer)

func client() {
    for {
        var b *Buffer
        // Grab a buffer if available; allocate if not.
        select {
        case b = <-freeList:
            // Got one; nothing more to do.
        default:
            // None free, so allocate a new one.
            b = new(Buffer)
        }
        load(b)              // Read next message from the net.
        serverChan <- b      // Send to server.
    }
}

The server loop receives each message from the client, processes it, and returns the buffer to the free list.
服务器循环接收来自客户端的每条消息,处理它,然后将缓冲区返回到空闲列表。

func server() {
    for {
        b := <-serverChan    // Wait for work.
        process(b)
        // Reuse buffer if there's room.
        select {
        case freeList <- b:
            // Buffer on free list; nothing more to do.
        default:
            // Free list full, just carry on.
        }
    }
}

The client attempts to retrieve a buffer from freeList; if none is available, it allocates a fresh one. The server's send to freeList puts b back on the free list unless the list is full, in which case the buffer is dropped on the floor to be reclaimed by the garbage collector. (The default clauses in the select statements execute when no other case is ready, meaning that the selects never block.) This implementation builds a leaky bucket free list in just a few lines, relying on the buffered channel and the garbage collector for bookkeeping.
客户端尝试从 freeList 获取一个缓冲区;如果没有可用的,则分配一个新的。服务器发送到 freeList 时会将 b 放回空闲列表,除非列表已满,在这种情况下,缓冲区会被丢弃,由垃圾回收器回收。( select 语句中的 default 子句在没有其他情况准备好时执行,这意味着 selects 永远不会阻塞。)该实现仅用几行代码构建了一个漏桶空闲列表,依赖缓冲通道和垃圾回收器进行管理。

Errors  错误 ¶

Library routines must often return some sort of error indication to the caller. As mentioned earlier, Go's multivalue return makes it easy to return a detailed error description alongside the normal return value. It is good style to use this feature to provide detailed error information. For example, as we'll see, os.Open doesn't just return a nil pointer on failure, it also returns an error value that describes what went wrong.
库函数通常必须向调用者返回某种错误指示。如前所述,Go 的多值返回使得返回详细的错误描述与正常返回值一起变得容易。使用此特性提供详细的错误信息是一种良好的编程风格。例如,正如我们将看到的, os.Open 不仅在失败时返回一个 nil 指针,还返回一个描述错误原因的错误值。

By convention, errors have type error, a simple built-in interface.
按惯例,错误的类型是 error ,这是一个简单的内置接口。

type error interface {
    Error() string
}

A library writer is free to implement this interface with a richer model under the covers, making it possible not only to see the error but also to provide some context. As mentioned, alongside the usual *os.File return value, os.Open also returns an error value. If the file is opened successfully, the error will be nil, but when there is a problem, it will hold an os.PathError:
库的编写者可以自由地用更丰富的模型实现该接口,使得不仅可以查看错误,还能提供一些上下文信息。如前所述,除了通常的 *os.File 返回值外, os.Open 还返回一个错误值。如果文件成功打开,错误值将是 nil ,但当出现问题时,它将包含一个 os.PathError

// PathError records an error and the operation and
// file path that caused it.
type PathError struct {
    Op string    // "open", "unlink", etc.
    Path string  // The associated file.
    Err error    // Returned by the system call.
}

func (e *PathError) Error() string {
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

PathError's Error generates a string like this:
PathErrorError 生成一个类似这样的字符串:

open /etc/passwx: no such file or directory

Such an error, which includes the problematic file name, the operation, and the operating system error it triggered, is useful even if printed far from the call that caused it; it is much more informative than the plain "no such file or directory".
这样的错误信息包含了有问题的文件名、操作以及触发的操作系统错误,即使打印位置远离引发错误的调用,也很有用;它比简单的“没有此类文件或目录”更具信息量。

When feasible, error strings should identify their origin, such as by having a prefix naming the operation or package that generated the error. For example, in package image, the string representation for a decoding error due to an unknown format is "image: unknown format".
在可行的情况下,错误字符串应标明其来源,例如通过添加一个前缀来命名生成错误的操作或包。例如,在包 image 中,由于未知格式导致的解码错误的字符串表示为 "image: unknown format"。

Callers that care about the precise error details can use a type switch or a type assertion to look for specific errors and extract details. For PathErrors this might include examining the internal Err field for recoverable failures.
关心精确错误细节的调用者可以使用类型切换或类型断言来查找特定错误并提取细节。对于 PathErrors ,这可能包括检查内部 Err 字段以获取可恢复的失败信息。

for try := 0; try < 2; try++ {
    file, err = os.Create(filename)
    if err == nil {
        return
    }
    if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
        deleteTempFiles()  // Recover some space.
        continue
    }
    return
}

The second if statement here is another type assertion. If it fails, ok will be false, and e will be nil. If it succeeds, ok will be true, which means the error was of type *os.PathError, and then so is e, which we can examine for more information about the error.
这里的第二个 if 语句是另一个类型断言。如果它失败, ok 将为 false, e 将是 nil 。如果成功, ok 将为 true,这意味着错误是 *os.PathError 类型,然后 e 也是该类型,我们可以检查它以获取更多错误信息。

Panic

The usual way to report an error to a caller is to return an error as an extra return value. The canonical Read method is a well-known instance; it returns a byte count and an error. But what if the error is unrecoverable? Sometimes the program simply cannot continue.
向调用者报告错误的常用方法是作为额外的返回值返回一个 error 。典型的 Read 方法是一个众所周知的例子;它返回字节数和一个 error 。但如果错误是不可恢复的呢?有时程序根本无法继续。

For this purpose, there is a built-in function panic that in effect creates a run-time error that will stop the program (but see the next section). The function takes a single argument of arbitrary type—often a string—to be printed as the program dies. It's also a way to indicate that something impossible has happened, such as exiting an infinite loop.
为此,有一个内置函数 panic ,实际上会创建一个运行时错误,从而停止程序(但请参见下一节)。该函数接受一个任意类型的单个参数——通常是字符串——在程序终止时打印出来。这也是表明发生了不可能的事情的一种方式,例如退出无限循环。

// A toy implementation of cube root using Newton's method.
func CubeRoot(x float64) float64 {
    z := x/3   // Arbitrary initial value
    for i := 0; i < 1e6; i++ {
        prevz := z
        z -= (z*z*z-x) / (3*z*z)
        if veryClose(z, prevz) {
            return z
        }
    }
    // A million iterations has not converged; something is wrong.
    panic(fmt.Sprintf("CubeRoot(%g) did not converge", x))
}

This is only an example but real library functions should avoid panic. If the problem can be masked or worked around, it's always better to let things continue to run rather than taking down the whole program. One possible counterexample is during initialization: if the library truly cannot set itself up, it might be reasonable to panic, so to speak.
这只是一个例子,但真正的库函数应避免使用 panic 。如果问题可以被掩盖或绕过,最好让程序继续运行,而不是让整个程序崩溃。一个可能的反例是在初始化期间:如果库确实无法完成自身的设置,所谓的恐慌(panic)可能是合理的。

var user = os.Getenv("USER")

func init() {
    if user == "" {
        panic("no value for $USER")
    }
}

Recover  恢复 ¶

When panic is called, including implicitly for run-time errors such as indexing a slice out of bounds or failing a type assertion, it immediately stops execution of the current function and begins unwinding the stack of the goroutine, running any deferred functions along the way. If that unwinding reaches the top of the goroutine's stack, the program dies. However, it is possible to use the built-in function recover to regain control of the goroutine and resume normal execution.
当调用 panic 时,包括隐式调用的运行时错误,如切片索引越界或类型断言失败,它会立即停止当前函数的执行,并开始展开该 goroutine 的调用栈,同时执行沿途的所有延迟函数。如果展开过程到达了 goroutine 调用栈的顶部,程序将终止。然而,可以使用内置函数 recover 来重新控制该 goroutine 并恢复正常执行。

A call to recover stops the unwinding and returns the argument passed to panic. Because the only code that runs while unwinding is inside deferred functions, recover is only useful inside deferred functions.
调用 recover 会停止展开过程并返回传递给 panic 的参数。因为在展开过程中唯一执行的代码是在延迟函数中,所以 recover 仅在延迟函数内部有用。

One application of recover is to shut down a failing goroutine inside a server without killing the other executing goroutines.
recover 的一个应用是在服务器内部关闭失败的 goroutine,而不终止其他正在执行的 goroutine。

func server(workChan <-chan *Work) {
    for work := range workChan {
        go safelyDo(work)
    }
}

func safelyDo(work *Work) {
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    do(work)
}

In this example, if do(work) panics, the result will be logged and the goroutine will exit cleanly without disturbing the others. There's no need to do anything else in the deferred closure; calling recover handles the condition completely.
在这个例子中,如果 do(work) 发生恐慌,结果将被记录,且该 goroutine 会干净地退出而不会干扰其他 goroutine。无需在延迟闭包中做其他操作;调用 recover 完全处理了该情况。

Because recover always returns nil unless called directly from a deferred function, deferred code can call library routines that themselves use panic and recover without failing. As an example, the deferred function in safelyDo might call a logging function before calling recover, and that logging code would run unaffected by the panicking state.
因为 recover 总是返回 nil ,除非直接从延迟函数中调用,所以延迟代码可以调用那些自身使用 panicrecover 的库例程而不会失败。例如, safelyDo 中的延迟函数可能会在调用 recover 之前调用一个日志函数,而该日志代码将在恐慌状态下不受影响地运行。

With our recovery pattern in place, the do function (and anything it calls) can get out of any bad situation cleanly by calling panic. We can use that idea to simplify error handling in complex software. Let's look at an idealized version of a regexp package, which reports parsing errors by calling panic with a local error type. Here's the definition of Error, an error method, and the Compile function.
有了我们的恢复模式, do 函数(以及它调用的任何函数)可以通过调用 panic 来干净利落地摆脱任何糟糕的情况。我们可以利用这个想法来简化复杂软件中的错误处理。让我们来看一个理想化的 regexp 包的版本,它通过调用 panic 并传入本地错误类型来报告解析错误。下面是 Error 的定义,一个 error 方法,以及 Compile 函数。

// Error is the type of a parse error; it satisfies the error interface.
type Error string
func (e Error) Error() string {
    return string(e)
}

// error is a method of *Regexp that reports parsing errors by
// panicking with an Error.
func (regexp *Regexp) error(err string) {
    panic(Error(err))
}

// Compile returns a parsed representation of the regular expression.
func Compile(str string) (regexp *Regexp, err error) {
    regexp = new(Regexp)
    // doParse will panic if there is a parse error.
    defer func() {
        if e := recover(); e != nil {
            regexp = nil    // Clear return value.
            err = e.(Error) // Will re-panic if not a parse error.
        }
    }()
    return regexp.doParse(str), nil
}

If doParse panics, the recovery block will set the return value to nil—deferred functions can modify named return values. It will then check, in the assignment to err, that the problem was a parse error by asserting that it has the local type Error. If it does not, the type assertion will fail, causing a run-time error that continues the stack unwinding as though nothing had interrupted it. This check means that if something unexpected happens, such as an index out of bounds, the code will fail even though we are using panic and recover to handle parse errors.
如果 doParse 发生恐慌,恢复块将把返回值设置为 nil ——延迟函数可以修改命名返回值。然后它会在赋值给 err 时检查问题是否是解析错误,通过断言它具有局部类型 Error 。如果不是,类型断言将失败,导致运行时错误,继续堆栈展开,就好像没有中断一样。这个检查意味着如果发生意外情况,比如索引越界,代码将失败,即使我们使用 panicrecover 来处理解析错误。

With error handling in place, the error method (because it's a method bound to a type, it's fine, even natural, for it to have the same name as the builtin error type) makes it easy to report parse errors without worrying about unwinding the parse stack by hand:
有了错误处理, error 方法(因为它是绑定到某个类型的方法,所以它与内置的 error 类型同名是可以的,甚至很自然)使得报告解析错误变得容易,无需手动展开解析栈:

if pos == 0 {
    re.error("'*' illegal at start of expression")
}

Useful though this pattern is, it should be used only within a package. Parse turns its internal panic calls into error values; it does not expose panics to its client. That is a good rule to follow.
虽然这种模式很有用,但它应该仅在包内使用。 Parse 将其内部的 panic 调用转换为 error 值;它不会向客户端暴露 panics 。这是一个很好的规则。

By the way, this re-panic idiom changes the panic value if an actual error occurs. However, both the original and new failures will be presented in the crash report, so the root cause of the problem will still be visible. Thus this simple re-panic approach is usually sufficient—it's a crash after all—but if you want to display only the original value, you can write a little more code to filter unexpected problems and re-panic with the original error. That's left as an exercise for the reader.
顺便说一句,这种重新 panic 的惯用法如果发生实际错误会改变 panic 值。然而,原始和新的失败都会在崩溃报告中显示,因此问题的根本原因仍然可见。因此,这种简单的重新 panic 方法通常足够——毕竟是崩溃——但如果你只想显示原始值,可以写更多代码来过滤意外问题并用原始错误重新 panic。这留给读者作为练习。

A web server  一个网络服务器 ¶

Let's finish with a complete Go program, a web server. This one is actually a kind of web re-server. Google provides a service at chart.apis.google.com that does automatic formatting of data into charts and graphs. It's hard to use interactively, though, because you need to put the data into the URL as a query. The program here provides a nicer interface to one form of data: given a short piece of text, it calls on the chart server to produce a QR code, a matrix of boxes that encode the text. That image can be grabbed with your cell phone's camera and interpreted as, for instance, a URL, saving you typing the URL into the phone's tiny keyboard.
让我们以一个完整的 Go 程序结束,这个程序是一个网络服务器。这个实际上是一种网络重定向服务器。谷歌在 chart.apis.google.com 提供了一项服务,可以自动将数据格式化为图表和图形。不过,它很难交互使用,因为你需要将数据作为查询放入 URL 中。这里的程序为一种数据形式提供了更好的界面:给定一小段文本,它调用图表服务器生成一个二维码,一个编码文本的方块矩阵。这个图像可以用手机摄像头捕捉并解释为例如一个 URL,省去了在手机小键盘上输入 URL 的麻烦。

Here's the complete program. An explanation follows.
这是完整的程序。以下是解释。

package main

import (
    "flag"
    "html/template"
    "log"
    "net/http"
)

var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18

var templ = template.Must(template.New("qr").Parse(templateStr))

func main() {
    flag.Parse()
    http.Handle("/", http.HandlerFunc(QR))
    err := http.ListenAndServe(*addr, nil)
    if err != nil {
        log.Fatal("ListenAndServe:", err)
    }
}

func QR(w http.ResponseWriter, req *http.Request) {
    templ.Execute(w, req.FormValue("s"))
}

const templateStr = `
<html>
<head>
<title>QR Link Generator</title>
</head>
<body>
{{if .}}
<img src="http://chart.apis.google.com/chart?chs=300x300&cht=qr&choe=UTF-8&chl={{.}}" />
<br>
{{.}}
<br>
<br>
{{end}}
<form action="/" name=f method="GET">
    <input maxLength=1024 size=70 name=s value="" title="Text to QR Encode">
    <input type=submit value="Show QR" name=qr>
</form>
</body>
</html>
`

The pieces up to main should be easy to follow. The one flag sets a default HTTP port for our server. The template variable templ is where the fun happens. It builds an HTML template that will be executed by the server to display the page; more about that in a moment.
直到 main 的部分应该很容易理解。一个标志为我们的服务器设置了默认的 HTTP 端口。模板变量 templ 是有趣的地方。它构建了一个 HTML 模板,服务器将执行该模板以显示页面;稍后会详细介绍。

The main function parses the flags and, using the mechanism we talked about above, binds the function QR to the root path for the server. Then http.ListenAndServe is called to start the server; it blocks while the server runs.
main 函数解析标志,并使用我们上面讨论的机制,将函数 QR 绑定到服务器的根路径。然后调用 http.ListenAndServe 来启动服务器;它会在服务器运行时阻塞。

QR just receives the request, which contains form data, and executes the template on the data in the form value named s.
QR 只是接收请求,该请求包含表单数据,并在名为 s 的表单值上执行模板。

The template package html/template is powerful; this program just touches on its capabilities. In essence, it rewrites a piece of HTML text on the fly by substituting elements derived from data items passed to templ.Execute, in this case the form value. Within the template text (templateStr), double-brace-delimited pieces denote template actions. The piece from {{if .}} to {{end}} executes only if the value of the current data item, called . (dot), is non-empty. That is, when the string is empty, this piece of the template is suppressed.
模板包 html/template 功能强大;这个程序只是触及了它的能力。本质上,它通过替换从传递给 templ.Execute 的数据项派生的元素,动态重写一段 HTML 文本,在本例中是表单值。在模板文本( templateStr )中,双大括号括起来的部分表示模板操作。从 {{if .}}{{end}} 的部分仅在当前数据项(称为 . (点))的值非空时执行。也就是说,当字符串为空时,这部分模板会被抑制。

The two snippets {{.}} say to show the data presented to the template—the query string—on the web page. The HTML template package automatically provides appropriate escaping so the text is safe to display.
这两个代码片段 {{.}} 表示在网页上显示呈现给模板的数据——查询字符串。HTML 模板包会自动提供适当的转义,因此文本显示是安全的。

The rest of the template string is just the HTML to show when the page loads. If this is too quick an explanation, see the documentation for the template package for a more thorough discussion.
模板字符串的其余部分只是页面加载时显示的 HTML。如果这个解释太简略,请参阅 template 包的文档以获得更详细的讨论。

And there you have it: a useful web server in a few lines of code plus some data-driven HTML text. Go is powerful enough to make a lot happen in a few lines.
就是这样:用几行代码加上一些数据驱动的 HTML 文本,就构建了一个有用的 Web 服务器。Go 足够强大,可以用很少的代码实现很多功能。