Notessh2a

Methods

In Go, a method is a function with a receiver argument associated with a specific type. It defines behavior for instances of that type.

Methods can only be defined on named types. They cannot be defined on unnamed types such as []int or map[string]int.

A named type is declared using the type keyword.

type myInt int
type person struct {
 name string
 age  int
}

Syntax:

func (receiver receiverType) methodName(parameters) returnType {
    // method body
}
  • receiver: The variable that represents the instance the method is called on.
  • receiverType: The type the method is attached to.
  • methodName: The name of the method being defined.
  • parameters: The input arguments the method accepts (optional).
  • returnType: The value type the method returns (optional).
  • Declare:

    type person struct {
        name string
        age  int
    }
    
    func (p person) sayHello() {
        fmt.Println("Hello, my name is " + p.name)
    }
    A method can only be declared on a receiver type defined in the same package.
  • Call:

    user := person{name: "John", age: 35}
    user.sayHello() // Hello, my name is John

Extra:

  • Pointer receivers.

    To modify original data for types with pass-by-value semantics, the method must use a pointer receiver.

    type myCounter int
    
    func (mc *myCounter) increment() {
    	*mc++
    }
    
    func main() {
    	count := myCounter(0)
    
    	count.increment()
    	count.increment()
    
    	fmt.Printf("count: %v\n", count) // count: 2
    }

    Another example:

    func (p *person) changeName(name string) {
    	p.name = name // `(*p).name = name` is also valid, but unnecessary in Go.
    }
    
    func main() {
    	user := person{name: "John", age: 35}
    
    	user.sayHello() // Hello, my name is John
    
    	user.changeName("Jane")
    
    	user.sayHello() // Hello, my name is Jane
    }

    Go automatically dereferences struct pointers (T to *T) when accessing fields. This is why p.name = name can be used instead of (*p).name = name.

    The automatic T to *T conversion only happens when the value is addressable.

    A value is addressable if it has a stable memory location.

    type person struct {
      name string
    }
    
    func (p *person) sayHello() {
      fmt.Println("Hello, my name is", p.name)
    }
    
    func main() {
      user := person{name: "John"}
    
      user.sayHello() // Hello, my name is John
    
      person{name: "John"}.sayHello() // <-- compiler: cannot call pointer method sayHello on person
    }

    user is a variable with a stable address. The compiler can take &user implicitly.

    person{name: "John"} is a temporary value without a stable address. The compiler cannot take its address, so the call does not compile.

    To make it work, take the address explicitly:

    (&person{name: "John"}).sayHello() // Hello, my name is John
  • Embedded structs with methods.

    Methods from an embedded struct are also promoted to the outer struct and can be called directly.

    type person struct {
    	name string
    	age  int
    }
    
    func (p person) sayHello() {
    	fmt.Println("Hello, my name is", p.name)
    }
    
    type employee struct {
    	person
    	company string
    }
    
    func main() {
    	user := person{name: "John", age: 35}
    	user.sayHello() // Hello, my name is John
    
    	user2 := employee{person{name: "Jane", age: 28}, "Google"}
    	user2.sayHello() // Hello, my name is Jane
    }

    If the outer struct defines a method with the same name, that method is used instead of the promoted one. The embedded method is shadowed but can still be accessed through the embedded field.

    func (e employee) sayHello() {
    	fmt.Println("Hello, my name is", e.name, "and I'm working at", e.company)
    }
    
    func main() {
    	user := person{name: "John", age: 35}
    	user.sayHello() // Hello, my name is John
    
    	user2 := employee{person{name: "Jane", age: 28}, "Google"}
    	user2.sayHello() // Hello, my name is Jane and I'm working at Google
    
    	user2.person.sayHello() // Hello, my name is Jane
    }