Behaviour belongs to the type
Fields give a type data, and methods give it behaviour: actions a value can perform. A Go method is a function with one extra parameter, the receiver, written in parentheses between func and the method name. The receiver says which type the method belongs to and gives the method a name for the value it was called on.
type Dog struct {
name string
}
func NewDog(name string) Dog {
return Dog{name: name}
}
func (d Dog) bark() {
fmt.Printf("Woof! I am %s\n", d.name)
}
Inside bark, the name d is the dog the method was called on, so d.name reads that dog's field. You call the method with the dot operator: dog.bark().
Methods live outside the struct
In Go a method is not declared inside the struct braces. The struct lists only data, and each method is a separate top-level declaration, which can sit anywhere in the same package. A type can have as many methods as it needs, and each one can take further parameters and return values like any function:
func (d Dog) describe(times int) string {
return strings.Repeat(d.name+" ", times)
}
The receiver name
The receiver is an ordinary parameter, so you pick its name. This lesson uses this, which is familiar from other languages. Go code in the wild usually uses a short name taken from the type, such as d for Dog, and keeps it the same across all methods of that type. The compiler accepts both, since this has no special meaning in Go. Use whichever name the lesson asks for here.
Value receivers and pointer receivers
The receiver in this lesson is a Dog, a value receiver, so the method works on a copy of the dog. That is fine for reading fields. A method that must change the value needs a pointer receiver, written (d *Dog). Running this shows the difference:
func (d Dog) birthdayValue() { d.age++ }
func (d *Dog) birthdayPointer() { d.age++ }
func main() {
d := NewDog("Rex", 4)
d.birthdayValue()
fmt.Println(d.age) // 4: only a copy changed
d.birthdayPointer()
fmt.Println(d.age) // 5
}
The first call increased the age of a copy, which was thrown away. The second changed d itself. Go takes the address of d for you in the second call because d is a variable you can address. As a rule, use a pointer receiver when the method modifies the value or when the struct is large, and use a value receiver otherwise.
The lesson's exercise
The starter code gives Dog and NewDog. The task is a bark method that prints Woof! I am followed by the name, and a mainFunction that uses it.
- Declare
barkbelowNewDog, with a receiver of typeDognamedthis, so the method can read the fields. - Print the message with
fmt.Printf. The%sverb inserts the name, and the format string ends in\nbecausePrintfdoes not add a newline. - Declare
mainFunction, the place where the program runs your code. - Create
rexwithNewDog("Rex")and callrex.bark().
type Dog struct {
name string
}
func NewDog(name string) Dog {
return Dog{name: name}
}
func (this Dog) bark() {
fmt.Printf("Woof! I am %s\n", this.name)
}
func mainFunction() {
rex := NewDog("Rex")
rex.bark()
}
It prints:
Woof! I am Rex
Common mistakes
- Calling the method as a plain function.
bark(rex)does not compile, becausebarkbelongs toDog. Writerex.bark(). - Forgetting the receiver. Without
(this Dog),func bark()is an ordinary function that has no access to any dog. - Expecting a value receiver to modify the dog. It changes a copy. Use a pointer receiver for that.
- Leaving
\nout of aPrintfformat.Printfprints exactly the format you give it, so the next output would continue on the same line. - Declaring two methods with the same name on one type. A type cannot have two methods called
bark, even with different parameters, because Go has no method overloading.
Key takeaways
- A method is a function with a receiver:
func (r Type) name(...). - It is declared outside the struct and called with the dot operator.
- The receiver name is your choice;
thisis allowed but short type-based names are the Go habit. - Value receivers get a copy, pointer receivers can modify the original.
- Go has no method overloading.