3

Let's assume I have the following function

func printNumbers(){
 var x int

 defer fmt.Println(x)

 for i := 0; i < 5; i++{
  x++
 }
}

As it is said in the specification:

Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked.

Obviously, zero will be printed out when the function execution ends. But what should I do if I want to print out the final value of variable x?

I've come up with the following solution:

func printNumbers(){
  var x int

  printVal := func(){
    fmt.Println(x)
  }

  defer printVal()

  for i := 0; i < 5; i++{
    x++
  }
}

So I wonder if there is a better way to resolve this problem.

icza
  • 389,944
  • 63
  • 907
  • 827
Zallin
  • 435
  • 1
  • 4
  • 5

2 Answers2

8

Generally what is important is x cannot be a parameter of the function you are deferring, because they are evaluated when defer is executed.

1) With Anonymous Function

Here's a solution using an anonymous function:

defer func() { fmt.Println(x) }()

Here x is not a parameter of the deferred anonymous function, so it will not be evaluated. Only when the anonymous function is executed and it calls fmt.Println().

2) With Pointer

Using a pointer (like &x) pointing to x would work, because only the address is evaluated, and the pointed value at the end will be 5 of course. The problem with this is that fmt.Println() will not print the pointed value but the pointer itself.

But to demonstrate its working, see this helper function:

func Print(i *int) {
    fmt.Println(*i)
}

And using it:

defer Print(&x) // Will print 5 at the end

3) With custom type

This is similar to the pointer solution, but doesn't need a helper function. But it does need you to write your String() method:

type MyInt int

func (m *MyInt) String() string {
    return strconv.Itoa(int(*m))
}

And using it:

var x MyInt

defer fmt.Println(&x)

for i := 0; i < 5; i++ {
    x++
}

When defer statement is executed, only the pointer will be evaluated (address of x, type of *Myint). And since the type *MyInt implements fmt.Stringer, fmt.Println() will call its String() method.

4) Wrapping

This is also similar to the pointer solution, and this will not even print exactly just 5 as you expect, but:

Problem with #2 was that fmt.Println() will print the pointer and not the pointed value (which we solved with our own Print() function). However there are other types which are similar to pointers and fmt.Println() will print their content.

So let's wrap the variable into a slice, and see what happens:

x := []int{0}

defer fmt.Println(x)

for i := 0; i < 5; i++ {
    x[0]++
}

Prints:

[5]

Reason for seeing 5 is that a slice is a descriptor. When defer is evaluated, a copy is made of the slice (that will be passed to fmt.Println() when it gets executed) but it refers to the same underlying array.

Also note that fmt.Println() prints the pointed content if the pointer is a pointer to a struct, array, slice, maps, so the following code also works:

x := struct{ i int }{}

defer fmt.Println(&x)

for i := 0; i < 5; i++ {
    x.i++
}

And prints:

&{5}
icza
  • 389,944
  • 63
  • 907
  • 827
6

If the defer has arguments they are evaluated at the line of the defer-statement; this is illustrated in the following snippet, where the defer will print 0:

func printNumber() {
   i := 0
   defer fmt.Println(i) // will print 0
   i++
   return
}

You can use an anonymous function as a defer statement if you want to postpone the execution of a statement or a function until the end of the enclosing (calling) function. Here is an updated example:

func printNumbers() {
    x := 0
    defer func() { fmt.Println(x) }()
    for i:=0; i < 5; i++ {
        x++;
    }
    return
}

http://play.golang.org/p/YQGQ_8a0_9

Endre Simo
  • 11,330
  • 2
  • 40
  • 49