Methods in Golang
In Go, methods are unique kinds of functions linked to a certain receiver, or data type. They enable you to add more behaviour to types by combining data and the operations that can be applied to it into a single unit. With this method, Go applies object-oriented design concepts without the need for conventional classes or inheritance, resulting in code that is more straightforward and reliable.
Defining a Method
A method is defined using a syntax identical to that of a conventional function, but before the function name comes an additional parameter called the receiver. In this receiver declaration, the type to which the method is associated is specified. Like any other parameter, the receiver behaves inside the method body. The Go community often favours using a lowercase version of the receiver type’s first character when naming the receiver variable.
Example of Method Definition:
package main
import "fmt"
// Define a struct type named Creature
type Creature struct {
Name string
Greeting string
}
// Greet is a method with a value receiver (c Creature)
func (c Creature) Greet() {
fmt.Printf("%s says %s!\n", c.Name, c.Greeting)
}
func main() {
// Create an instance of Creature
sammy := Creature{
Name: "Sammy",
Greeting: "Hello",
}
// Call the Greet method using dot notation
sammy.Greet()
}
Output
Sammy says Hello!
The Greet method’s recipient in this instance is (c Creature).
Calling Methods
Usually, methods are called using dot notation, which consists of the method name followed by a dot (.) and a variable of the proper type. Because it is easier to read and clearly illustrates the order of method invocations, this style is favoured by the Go community and the Go standard library.
Value Receivers vs. Pointer Receivers
The ability of the method to alter the original instance of the type to which it is attached depends on the type of receiver (value or pointer).
Value Receivers
- The struct instance is copied when a method with a value recipient (such as (c Creature)) is used.
- The original struct variable outside the function will not be impacted by any changes made to the receiver’s fields within the method because it is operating on a duplicate.
- The function argument “pass by value” is comparable to this.
Pointer Receivers
- A method receives a pointer to the original struct instance when it has a pointer receiver (for example, (c *Creature)).
- As a result, the procedure can alter the fields of the original instance.
- Go provides an “ergonomic amenity” (automatic dereferencing) where you don’t need to explicitly use the dereference operator (
*
) when calling a method on a pointer;myCreaturePtr.Method()
works just like(*myCreaturePtr).Method()
. Similarly, when calling a method with a pointer receiver on a non-pointer variable, Go will automatically take the address of the variable (e.g.,myCreature.Method()
becomes(&myCreature).Method()
). - If any of a type’s methods need to change the instance, it is usually recommended to use pointer receivers consistently across all of them.
Example Illustrating Value vs. Pointer Receivers:
package main
import "fmt"
type Fish struct {
Species string
IsUnderwater bool
}
// SetSpeciesValue is a method with a VALUE receiver
func (f Fish) SetSpeciesValue(newSpecies string) {
f.Species = newSpecies // This changes the COPY, not the original
fmt.Printf("Inside SetSpeciesValue (copy): %+v\n", f)
}
// Dive is a method with a POINTER receiver
func (f *Fish) Dive() {
f.IsUnderwater = true // This changes the ORIGINAL
fmt.Printf("Inside Dive (original): %+v\n", f)
}
// Surface is a method with a POINTER receiver
func (f *Fish) Surface() {
f.IsUnderwater = false // This changes the ORIGINAL
fmt.Printf("Inside Surface (original): %+v\n", f)
}
func main() {
// --- Demonstrating Value Receiver ---
salmon := Fish{Species: "Salmon", IsUnderwater: false}
fmt.Printf("Before SetSpeciesValue: %+v\n", salmon)
salmon.SetSpeciesValue("Trout") // Calls method on a copy
fmt.Printf("After SetSpeciesValue: %+v\n", salmon)
fmt.Println("---")
// --- Demonstrating Pointer Receivers ---
shark := Fish{Species: "Great White", IsUnderwater: false}
fmt.Printf("Before Dive: %+v\n", shark)
shark.Dive() // Calls method on pointer receiver (Go takes &shark automatically)
fmt.Printf("After Dive: %+v\n", shark)
fmt.Println("---")
shark.Surface() // Call Surface method
fmt.Printf("After Surface: %+v\n", shark)
}
Output
Before SetSpeciesValue: {Species:Salmon IsUnderwater:false}
Inside SetSpeciesValue (copy): {Species:Trout IsUnderwater:false}
After SetSpeciesValue: {Species:Salmon IsUnderwater:false}
---
Before Dive: {Species:Great White IsUnderwater:false}
Inside Dive (original): &{Species:Great White IsUnderwater:true}
After Dive: {Species:Great White IsUnderwater:true}
---
Inside Surface (original): &{Species:Great White IsUnderwater:false}
After Surface: {Species:Great White IsUnderwater:false}
Purpose and Benefits of Methods
Organized Code: By directly tying behaviour to the data types they work with, methods offer a clear structure for code organization that facilitates reading, comprehending, and maintaining the code.
Behavior to Data: They let you specify a type’s “can do” as well as its “is.”
Encapsulation: Although Go lacks classes, methods and structs work similarly to be able to capture state and behaviour.
Composition: Methods are essential to Go’s composition model because types can “forward” their methods automatically and incorporate other types, allowing for code reuse without the need for conventional inheritance hierarchies.
Interfaces: The “method set” a list of methods a type must have is defined by methods, which are essential to interfaces and enable polymorphism (various shapes) in Go. The implicit implementation of all an interface’s methods by any type “satisfies” the interface.
Within the same package, methods can be attached to any user-declared type, including slices, structs, and even simple types like float64 (when aliased with type newType float64). They are a flexible tool that can be used to efficiently organise Go programs.
You can also read What Are The Pointers In Golang With Clear Code Examples