Skip to content

Latest commit

 

History

History
188 lines (136 loc) · 9.45 KB

07.Functions.md

File metadata and controls

188 lines (136 loc) · 9.45 KB

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 语言一些笨拙的编码风格:带内错误返回值,例如用 -1 表示 EOF ,还有通过传址修改参数。

译注:带内(in-band)和带外(out-of-band)是通讯领域的概念,《UNIX网络编程》卷一第 24 章有关于带外数据的描述,带内数据资料甚少,有熟悉的朋友可以提供一下,另 Error Handling 一文高屋建瓴,立意颇深,有兴趣的读者可以一观。

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 中,通过一个负的写入数量来表示 write 发生错误 ,而且其错误码隐藏在一个很不稳定的位置(译注:这里指 errno 是一个全局变量,errno 的值只有在一个库函数调用发生错误时才会被设置,当库函数调用成功运行时,errno 的值不会被修改,当然也不会主动被置为 0)。在 Go 中,Write 可以同时返回写入数量和一个 error:“没错,因为磁盘爆满,并非所有的字节都被成功写入”。os 包中关于file的写入函数签名为:

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.

正如文档所示,当 n != len(b) 时,它返回已写入的字节数和一个非 nil 的 error。这是一种常见风格,更多示例见错误处理章节。

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为例,把一个函数调用延期执行有两点好处。第一,保证你永远不会忘记关闭文件,尤其是后期修改函数增加新的 return 路径时。第二,使用 defer 意味着 Close函数与Open函数邻近,这比写在函数末尾要清晰得多。

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 语句执行那一刻求值,并非是函数执行那一刻。这意味着一个 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:

被 defer 的函数按后进先出的顺序执行,故这段代码会依次打印 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 时被求值这一事实,我们可以进一步完善追踪程序。tracing 例程可以设置参数到 untracing 例程。示例如下:

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.

对习惯于其它编程语言中 block-level 资源管理的开发者来说,defer稍显怪异,但其最引人入胜、最强大的应用正是来自于它是 function-based 而不是 block-based 这一事实。我们将会在panicrecover章节探索 defer 其它可能的用法。