简体   繁体   中英

Passing parameters to function closure

I'm trying to understand the difference in Go between creating an anonymous function which takes a parameter, versus having that function act as a closure. Here is an example of the difference.

With parameter:

func main() {
  done := make(chan bool, 1)
  go func(c chan bool) {
    time.Sleep(50 * time.Millisecond)
    c <- true
  }(done)
  <-done
}

As closure:

func main() {
  done := make(chan bool, 1)
  go func() {
    time.Sleep(50 * time.Millisecond)
    done <- true
  }()
  <-done
}

My question is, when is the first form better than the second? Would you ever use a parameter for this kind of thing? The only time I can see the first form being useful is when returning a func(x, y) from another function.

The difference between using a closure vs using a function parameter has to do with sharing the same variable vs getting a copy of the value. Consider these two examples below.

In the Closure all function calls will use the value stored in i . This value will most likely already reach 3 before any of the goroutines has had time to print it's value.

In the Parameter example each function call will get passed a copy of the value of i when the call was made, thus giving us the result we more likely wanted:

Closure:

for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i)
    }()
}

Result:

3
3
3

Parameter:

for i := 0; i < 3; i++ {
    go func(v int) {
        fmt.Println(v)
    }(i)
}

Result:

0
1
2

Playground: http://play.golang.org/p/T5rHrIKrQv

When to use parameters

Definitely the first form is preferred if you plan to change the value of the variable which you don't want to observe in the function.

This is the typical case when the anonymous function is inside a for loop and you intend to use the loop's variables, for example:

for i := 0; i < 10; i++ {
    go func(i int) {
        fmt.Println(i)
    }(i)
}

Without passing the variable i you might observe printing 10 ten times. With passing i , you will observe numbers printed from 0 to 9 .

When not to use parameters

If you don't want to change the value of the variable, it is cheaper not to pass it and thus not create another copy of it. This is especially true for large structs. Although if you later alter the code and modify the variable, you may easily forget to check its effect on the closure and get unexpected results.

Also there might be cases when you do want to observe changes made to "outer" variables, such as:

func GetRes(name string) (Res, error) {
    res, err := somepack.OpenRes(name)
    if err != nil {
        return nil, err
    }

    closeres := true
    defer func() {
        if closeres {
            res.Close()
        }
    }()

    // Do other stuff
    if err = otherStuff(); err != nil {
        return nil, err // res will be closed
    }

    // Everything went well, return res, but
    // res must not be closed, it will be the responsibility of the caller
    closeres = false

    return res, nil // res will not be closed
}

In this case the GetRes() is to open some resource. But before returning it other things have to be done which might also fail. If those fail, res must be closed and not returned. If everything goes well, res must not be closed and returned.

This is a example of parameter from net/Listen

package main

import (
    "io"
    "log"
    "net"
)

func main() {
    // Listen on TCP port 2000 on all available unicast and
    // anycast IP addresses of the local system.
    l, err := net.Listen("tcp", ":2000")
    if err != nil {
        log.Fatal(err)
    }
    defer l.Close()
    for {
        // Wait for a connection.
        conn, err := l.Accept()
        if err != nil {
            log.Fatal(err)
        }
        // Handle the connection in a new goroutine.
        // The loop then returns to accepting, so that
        // multiple connections may be served concurrently.
        go func(c net.Conn) {
            // Echo all incoming data.
            io.Copy(c, c)
            // Shut down the connection.
            c.Close()
        }(conn)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM