Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go closure variable scope

Tags:

closures

go

I'm reading 'CreateSpace An Introduction to Programming in Go 2012'

and on page 86 I found this evil magic

func makeEvenGenerator() func() uint {
    i := uint(0)

    return func() (ret uint) {
        ret = i
        i += 2
        return
    }
}

// here's how it's called
nextEven := makeEvenGenerator()
fmt.Println(nextEven())
fmt.Println(nextEven())
fmt.Println(nextEven())

1) Why is i not resetting ? 2) is nextEven() returning and uint or is Println so smart that it can work with everything ?

like image 853
waaadim Avatar asked Nov 13 '13 15:11

waaadim


People also ask

What is the scope of a variable in Go?

The Scope of a variable can be defined as a part of the program where a particular variable is accessible. A variable can be defined in a class, method, loop, etc. Like C/C++, in Golang all identifiers are lexically (or statically) scoped, i.e. scope of a variable can be determined at compile time.

Does Go support closure?

Go functions may be closures. A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables; in this sense the function is "bound" to the variables. For example, the adder function returns a closure.

What are the two types of variable scope?

There are mainly two types of variable scopes: Local Variables. Global Variables.

What are the scopes of variables?

In simple terms, scope of a variable is its lifetime in the program. This means that the scope of a variable is the block of code in the entire program where the variable is declared, used, and can be modified.


2 Answers

For the sake of clarity, I'll assign names to both functions:

func makeEvenGenerator() func() uint { // call this "the factory"
    i := uint(0)

    return func() (ret uint) { // call this "the closure"
        ret = i
        i += 2
        return
    }
}

The factory returns the closure – functions are first class citizens in Go i.e. they can be right-hand expressions, for example:

f := func() { fmt.Println("f was called"); }

f() // prints "f was called"

In your code, the closure wraps over the context of the factory, this is called lexical scoping. This is why the variable i is available inside the closure, not as a copy but as a reference to i itself.

The closure uses a named return value called ret. What this means is that inside the closure you'll have implicitly declared ret and at the point of return, whatever value ret has will be returned.

This line:

ret = i

will assign the current value of i to ref. It will not change i. However, this line:

i += 2

will change the value of i for the next time the closure is called.


Here you'll find a little closure example I wrote together for you. It's not extremely useful but illustrates the scope, purpose and use of closures pretty well in my opinion:

package main

import "fmt"

func makeIterator(s []string) func() func() string {
    i := 0
    return func() func() string {
        if i == len(s) {
            return nil
        }
        j := i
        i++
        return func() string {
            return s[j]
        }
    }
}

func main() {

    i := makeIterator([]string{"hello", "world", "this", "is", "dog"})

    for c := i(); c != nil; c = i() {
        fmt.Println(c())
    }

}
like image 145
thwd Avatar answered Sep 20 '22 05:09

thwd


1) Why is i not resetting ?

Closures in Go capture variables by reference. That means the inner function holds a reference to the i variable in the outer scope, and each call of it accesses this same variable.

2) is nextEven() returning and uint or is Println so smart that it can work with everything ?

fmt.Println() (along with fmt.Print(), fmt.Fprint(), etc.) can work most types. It prints its arguments in the "default format". It is the same thing that is printed using fmt.Printf() using the %v verb.

like image 37
newacct Avatar answered Sep 22 '22 05:09

newacct