Summary of Notes on the Use of Go Functions (Escape Analysis, Anonymous Functions, Closure Functions)

Function declaration method:

func function name (parameter list) (return value list) {
	execute statement
    return return list of values
}
  • 1
  • 2
  • 3
  • 4

Stack area: The stack area of ​​the Go language generally stores basic data types, and the compiler has an escape analysis

Heap area: The heap area of ​​the Go language generally stores reference data types, and the compiler has an escape analysis

Children's shoes without foundation may be more confusing. What is escape analysis? First, you need to know what a memory escape is.

Memory escape :

In C and C++, it is often forgotten to release the memory after allocating it, which leads to memory leaks. A large number of memory leaks are fatal to the program. In the C language, as long as it is not malloc , global variables, and static local variables, all local variables are allocated in the stack area. When the function returns the address of a local variable, we call this variable trying to escape, that is, memory escape. .

Knowing what memory escape is, let's briefly introduce what escape analysis is.

In the process of compiling the Go language, it will be carried outescape analysis, that is, analyze this variable, or whether this memory wants to escape, if you want to escape, allocate it in the heap area; otherwise, allocate it in the stack area.

Benefits of escape analysis :

  • Makes memory allocation more reasonable. To put it bluntly, it is "find the most suitable place to stay". When you use malloc/new to apply for a piece of memory, the compiler finds that you have not used it after the function exits, and will store it in the stack area. In the same way, if an ordinary variable is analyzed by the compiler and it is still referenced elsewhere after the function is launched, it will be allocated in the heap area.
  • Reduced GC [garbage collection] pressure. If variables are allocated on the heap, unlike the stack, the heap can be cleaned up automatically. It will cause Go to perform garbage collection frequently, and garbage collection will take up a relatively large system overhead
  • Improve efficiency. Compared with the stack, the allocation speed of the heap is significantly lower than that of the stack, because the heap allocates memory and needs to find the appropriate memory block one by one through the pointer.

Basic principles of Go escape analysis :

An escape occurs when a function returns a reference to a variable. If after the function returns, it is determined that the variable is no longer referenced, it will be allocated on the stack, otherwise the compiler will allocate the variable on the heap. Also, if a local variable is very large, it should also be allocated on the heap rather than the stack.

There are several situations in which a memory escape occurs :

  • local variables are returned

  • interface{}dynamic type

    Many function parameters are interface{} empty interface types, which will cause escape, such asfunc Println(a ...interface{}) (n int, err error)

    func Printf(format string, a ...interface{}) (n int, err error)

  • Insufficient stack space

    For example, if you allocate a slice of very large memory to the stack space, an escape will occur

Disadvantages of memory escape :

Question: Is function passing pointer really more efficient than passing value?

We know that passing pointers can reduce the copying of the underlying value and improve efficiency, but if the amount of data copied is small, because pointer passing will cause escape, the heap may be used, and the burden of GC may be increased, so passing pointers is not necessarily efficient. of.

Everything has its advantages and disadvantages, the important thing is to use it in the right place, in order to give full play to its advantages

Reference article:
Golang memory allocation escape analysis
Golang memory allocation escape analysis

Well, the above is just a small episode, and interested children's shoes can also continue to learn more. Next we continue to understand the functions of the Go language.

Go functions support returning multiple values, which is unique compared to other languages

func  SubAdd ( a , b int )  ( int , int ) { 
	return a + b , a - b   //return sum and difference 
}
  • 1
  • 2
  • 3

Note: If multiple values ​​are returned, when receiving, you want to ignore a return value, then use the _ symbol to place the ignore

Notes and detailed discussion of function usage:

  1. The function return value list can be multiple, and supports naming the function return value

    func  SubAdd ( a , b int )  ( sum int , sub int ) { 
    	sum , sub = a + b , a - b
    	 return  
    }
    
    • 1
    • 2
    • 3
    • 4
  2. The variables in the function are local and do not take effect outside the function.

  3. Basic data types [int, float, bool, etc.] and arrays are pass-by-value by default. Modifications within the function will not affect the original value.

  4. Go functions do not support function overloading.

  5. Function in Go is also a data type, which can be assigned to a variable, then the variable is a variable of function type, and then the function can be called through this variable.

  6. Since the function is a data type, it can be used as a formal parameter and called.

  7. Support for variable parameters

    func  sum ( args ...  int ) sum int { // supports 0 or more parameters
        
    } 
    func  sum ( n1 int , args ...  int ) sum int { //support 1 or more parameters
       
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    If a function has variable parameters in the parameter list, the variable parameters need to be placed at the end of the parameter list

    //add.go 
    func  Sum ( n1 int , args ...  int )  int { 
    	sum := n1
    	 for i :=  0 ; i <  len ( args ) ; i ++ { 
    		sum += args [ i ] 
    	} 
    	return sum
     } 
    //main.go 
    fmt . Println ( oper . Sum ( 0 , 1 , 2, 3 , 4 , 5 ) )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

init function

Each source file can contain an init function, which will be called by the Go runtime framework before the main function is executed, that is, init will be called before the main function.

func  init ( ) { 
	fmt . Println ( "now is init..." ) 
} 
func  main ( )   { 
	fmt . Println ( "now is main..." ) 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

now is init…
now is main…

Init function usage details :

  1. If a file contains global variable definition, init function and main function at the same time, the execution flow is: global variable definition -> init function -> main function.

    Further thinking: If both main.go and utils.go contain variable definitions, init functions and main functions, what is the execution flow?

    insert image description here

  2. The main function of the init function is to complete some initialization work.

    For example, if you want to use global variables in the main function, you can initialize them through init first.

Anonymous function :

An anonymous function is a function without a name. If we only want to use a function once, we can consider using an anonymous function. Of course, anonymous functions can also be called multiple times.

  • Mode 1: Call it directly when defining an anonymous function, which can only be called once.

    func  main ( )   { 
    	res :=  func  ( n1 , n2 int )  int { 
    		return n1 + n2
    	 } ( 10 , 3 ) 
    	fmt . Println ( res ) 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • Method 2: Assign an anonymous function to a variable, and then call the anonymous function through the variable

    func  main ( ) { 
    	a :=  func  ( n1 int , n2 int )  int { 
    		return n1 + n2
    	 } 
    	res :=  a ( 12 , 1 ) 
    	fmt . Println ( res ) 
    	res =  a ( 12 , 143 ) 
    	fmt . Println ( res ) 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • Method 3: Global anonymous function, if the anonymous function is assigned as a global variable, then this is a global anonymous function, which can be valid in the program

    var  ( 
    	a =  func  ( n1 int , n2 int )  int { 
    		return n1 + n2
    	 } 
    ) 
    func  main ( ) { 
    	res :=  a ( 12 , 1 ) 
    	fmt . Println ( res ) 
    	res =  a ( 12 , 143 ) 
    	fmt . Println ( res ) 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

Closure :

A closure is a combination of a function and its associated reference environment.

//The accumulator closure returns a function func(int)int 
func  Addupper ( )  func ( int ) int { 
	//The following is equivalent to a whole (closure) 
	var n int  =  10 
	return  func  ( x int )  int { 
		n + = x
		 return n
	 } 
}

func  main ( ) { 
	//define the contents of a closure as a whole 
	f :=  Addupper ( ) 
	fmt . Println ( f ( 1 ) ) //10+1 = 11 
	fmt . Println ( f ( 2 ) ) // 11+2 = 13 
	fmt . Println ( f ( 3 ) ) //13+3 = 16 
	//retrieve a closure 
	f =  Addupper ( ) 
	fmt . Println ( f( 2 ) ) //10+2 = 12 
	fmt . Println ( f ( 4 ) ) //12+4 = 16 
	fmt . Println ( f ( 6 ) ) // 16+6 = 22 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

What is returned is an anonymous function, but this anonymous function refers to n outside the function, so this anonymous function and n form a whole, forming a closure

It can be understood in this way that a closure is a class, a function is an operation, and n is a field. The key to figuring out the closure is to analyze which variables the returned function uses, because the function and the variables it references together form the closure

Another example:

Write a function Makesuffix(suffix string) func(string) string, which can receive a file suffix and return a closure. When calling the closure, you can pass in a file name. If the file name does not have a specified suffix, it will return the file name + suffix. Otherwise, if there is a suffix, return the source file.

func  Makesuffix ( suffix string )  func ( string )  string { 
	return  func  ( name string ) string { 
		if  ! strings . HasSuffix ( name , suffix ) { 
			return name + suffix
		 } 
		return name
	 } 
}

func  main ( ) { 
	//define the content of a closure as a whole 
	f :=  Makesuffix ( ".jpg" ) 
	fmt . Println ( f ( "asdjh" ) ) 
	fmt . Println ( f ( "ernsasdh.jpg" ) ) 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

defer delay mechanism

When we need to create resources, in order to release resources in time after the function is executed, we have defer

Notes on using defer:

  1. When Go executes a defer, it will not execute the statement after the defer immediately, but push the statement after the defer into a stack, and then continue to execute the next statement of the function.
  2. When the function is executed, it will take out the statement from the top of the stack and execute it from the defer stack in turn [first in, last out]
  3. When defer puts the statement on the stack, it will also copy the related value and push it on the stack at the same time

For example, when we create a resource (such as opening a file, acquiring a database connection, locking a resource, etc.) we can executedefer file.Close() defer connect.Close(), After defer, you can continue to use the created resource. When the function is executed, the system will take out the statement from the defer stack and close the resource in turn.

func  main ( ) { 
	n :=  10 
	defer fmt . Println ( n )  //Create a copy of the value of n and push the statement onto the defer stack 
	n =  11 
	defer fmt . Println ( n ) 
	n =  12 
	defer fmt . Println ( n ) 
	n =  13 
	fmt . Println ( n ) 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

13
12
11
10

Function parameter passing method :

Value type parameters are passed by value by default, and reference type parameters are passed by reference by default.

In essence, pass-by-value and pass-by-reference actually pass copies of variables, but pass-by-value copies the value, while pass-by-reference copies the address.

Value types: basic data types, arrays, structures

Reference types: pointer, slice, map, pipe, interface

Variable scope :

  1. The variables declared/defined inside the function are all local variables, and the scope is limited to the inside of the function
  2. A variable declared/determined outside a function is called a global variable, and the scope is valid throughout the package. If its first letter is capitalized, the scope is valid throughout the program
  3. If the variable is in a code block, the scope is in this code block

Related: Summary of Notes on the Use of Go Functions (Escape Analysis, Anonymous Functions, Closure Functions)