Methods
There are no classes in Go. But, we can define methods on types.
A method is a function with special receiver argument. The receiver appears in its own argument list between the func keyword and the method name.
Example (Method)
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Dist() float64 {
return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
func main() {
v := Vertex{5, 12}
fmt.Println(v.Dist())
}13Example (Regular Function)
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func Dist(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{5, 12}
// written in regular function form
// pass in v Vertex as paramter
// same functionality
fmt.Println(Dist(v))
}13Methods on Non-Struct Types
We can declare a method on non-struct types, too.
In this example we see a numeric type MyFloat with an Abs method.
You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).
package main
import (
"fmt"
"math"
)
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}1.4142135623730951Pointer Receivers
We can declare methods with pointer receivers. The receiver type has *T for some type T (Note that T cannot be a pointer itself, say *int)
Example 1 (Pointer Receiver)
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}50Methods with pointer receivers can modify the value to which the receiver points (as Scale does here). Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
Example 2 (Value Receiver)
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(10)
fmt.Println(v.Abs())
}5Now, with a value receiver, the original Vertex value is not changed. This is because the Scale method operates on a copy of the original Vertex value. (This is the same behavior as for any other function argument.) The Scale method must have a pointer receiver to change the Vertex value declared in the main function.
Example 3 (Function Version)
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
Scale(&v, 10)
fmt.Println(Abs(v))
}50Here are the Abs and Scale methods rewritten as functions. If we change this to passing by value (instead of reference) by just removing the * from line 16, the code would not compile:
./prog.go:23:8: cannot use &v (type *Vertex) as type Vertex in argument to ScaleWe also need to change &v to v. And the output is:
5Methods and Pointer Indirection 1
The previous examples lead us to some differences the pointer and value receivers in actual code writing.
Example 4
package main
import "fmt"
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) { // Method
v.X = v.X * f
v.Y = v.Y * f
}
func ScaleFunc(v *Vertex, f float64) { // Function
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
v.Scale(2) // {6, 8}
ScaleFunc(&v, 10) // {60, 80}
p := &Vertex{4, 3}
p.Scale(3) // {12, 9}
ScaleFunc(p, 8) // {96, 72}
fmt.Println(v, p)
}{60 80} &{96 72}In this example, for the function version, the function with a pointer argument must take a pointer.
var v Vertex
ScaleFunc(v, 5) // Compile error as we saw in Example 3
ScaleFunc(&v, 5) // OKHowever, for the method version, the method with pointer receivers take either a value of a pointer as the receiver when they are called:
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(5) // OK For the statement v.Scale(5), even though v is a value and not a pointer, the method with the pointer receiver is called automatically. That is, as a convenience, Go interprets the statement v.Scale(5) as (&v).Scale(5) since the Scale method has a pointer receiver. (&v).Scale(5) still works certainly!
Methods and Pointer Indirection 2
Conversely, we have the same thing.
Example
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
fmt.Println(AbsFunc(v))
p := &Vertex{4, 3}
fmt.Println(p.Abs())
fmt.Println(AbsFunc(*p))
}5
5
5
5For functions that take a value argument, they must take a value of that specific type.
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // Compile errorFor methods with value receivers, they take either a value or a pointer as the receiver when they are called:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OKChoosing a Value or Pointer Receiver
Two main reasons:
To modify the value that its receiver points to
To avoid copying the values on each method call. For example, if the receiver is a large struct, this can be efficient.
Last updated
Was this helpful?