45

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.

Dave
  • 4,356
  • 4
  • 37
  • 40
  • 1
    https://github.com/golang/go/wiki/CommonMistakes - This will give clear picture for the doubt regarding closure in Go – abby37 May 17 '20 at 07:11

3 Answers3

63

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

ANisus
  • 74,460
  • 29
  • 162
  • 158
  • 1
    Thanks! That makes sense. A question then with regards to my original code; am I correct in saying that I should just use the channel as is without bothering with parameters? It seems like passing it through via parameter would just incur overhead. – Dave May 12 '15 at 11:34
  • Welcome :) Yes, in your case it wouldn't make any sense using parameters. A channel is also 'goroutine'-safe so to say. – ANisus May 12 '15 at 12:01
16

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.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks for this. This and the other answer really helped me clear up my understanding. Good examples. In my case, using a channel, I think I'll just use the closure style. – Dave May 12 '15 at 11:38
-1

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)
    }
}