Method Expressions

For a type TT.M is a function that is callable as a regular function with the same arguments as M prefixed by an additional argument that is the receiver of the method

For example, given:

type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver
  • T.Mv yields a function with the signature func(tv T, a int) int
  • (*T).Mp yields a function with the signature func(tp *T, f float32) float32
  • (*T).Mv yields a function with the signature func(tv *T, a int) int. This function indirects through the receiver to create a value to pass as the receiver to the underlying method
  • T.Mp is illegal, because pointer-receiver methods are not in the method set of the value type

It is legal to derive a function value from a method of an interface type. The resulting function takes an explicit receiver of that interface type

Method Values

If x has type T, the method value x.M is a function value that is callable with the same arguments as a method call of x.M

The expression x is evaluated and saved during the evaluation of the method value; the saved copy is then used as the receiver in any calls, which may be executed later

For example, given:

type T struct {
	a int
}
func (tv  T) Mv(a int) int         { return 0 }  // value receiver
func (tp *T) Mp(f float32) float32 { return 1 }  // pointer receiver
 
var t T
var pt *T
  • t.Mv yields a function value of type func(int) int
  • pt.Mp yields a function value of type func(float32) float32
  • pt.Mv is equivalent to (*pt).Mv
  • t.Mp is equivalent to (&t).Mp if t is addressable

Capturing Values in Closures

Closures may refer to variables defined in a surrounding function (free variables). Those variables are then shared between the surrounding function and the closure, and they survive as long as they are accessible

Closed over variables semantically are always captured by address, meaning they escape to the heap
However, internally, a closure can be passed a copy of a value (not the address) to reduce GC pressure. For example, if the value is a constant or is never modified

Implementation

Direct Call

A compiler rewrites a direct call of a function literal into a normal function call with closure variables passed as arguments. This avoids allocation of a closure object

For example, this call:

func(a int) {
	println(byval)
	byref++
}(42)

becomes:

func(byval int, byref *int, a int) {
	println(byval)
	(*byref)++
}(byval, byref, 42)

Function Values and Closure Object

Function values are pointers that point to structs. These structs contain either:

  1. Just the pointer to the function code for simple functions
  2. Pointers to an autogenerated wrapper function and receivers and/or function parameters in the case of method calls and closures

For example, this closure:

func closure() func() *byte {
    var b [4096]byte
    return func() *byte {
        return &b[0]
    }
}

becomes:

 
type closure_1_obj struct {
	F uintptr
	b *[4096]byte
}
 
func closure_1(p *closure_1_obj) *byte {
	return &p.b[0]
}
 
func closure() *struct{ F uintptr } {
	var b [4096]byte
	p := &closure_1_obj{
		F: &closure_1,
		b: &b,
	}
	return (*struct{ F uintptr })(unsafe.Pointer(p))
}

References