Golang Methods Tutorial with Examples
Published Updated Golang 11 min read
Value against pointer receivers and the method sets that follow from the choice, why a type with one pointer method stops satisfying an interface by value, methods on non-struct types, and method values.
A method is a function with a receiver. That is the entire mechanism: Go has no classes, and a method is not stored inside the type. It is declared alongside it:
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
(r Rectangle) is the receiver, and choosing between that and (r *Rectangle) is the only genuinely
interesting decision here, because it silently determines which interfaces the type satisfies.
Written against Go 1.22.
Value and pointer receivers
func (r Rectangle) Area() float64 { // value: operates on a copy
return r.Width * r.Height
}
func (r *Rectangle) Scale(factor float64) { // pointer: mutates the original
r.Width *= factor
r.Height *= factor
}
A value receiver gets a copy, so it cannot change the caller’s value. A pointer receiver can.
Go inserts the address-of or the dereference for you when the value is addressable, so both read the same at the call site:
r := Rectangle{3, 4}
fmt.Println(r.Area()) // value receiver on a value
r.Scale(2) // Go rewrites this as (&r).Scale(2)
fmt.Println(r.Width) // 6
p := &Rectangle{3, 4}
fmt.Println(p.Area()) // Go rewrites this as (*p).Area()
p.Scale(2)
Addressability is the limit. A map entry and a function’s return value have no address, so the rewrite cannot happen:
m := map[string]Rectangle{"a": {3, 4}}
m["a"].Scale(2) // compile error: cannot call pointer method on m["a"]
Use map[string]*Rectangle when entries must be mutated.
Method sets, and why this matters
Every type has a method set, and this is the rule that catches people:
- the method set of
Tcontains methods with value receivers - the method set of
*Tcontains methods with value and pointer receivers
So a pointer can call everything, and a value cannot. That is invisible until an interface is involved:
type Shape interface {
Area() float64
}
type Scalable interface {
Scale(float64)
}
var s Shape = Rectangle{3, 4} // fine: Area has a value receiver
var t Scalable = Rectangle{3, 4} // compile error
var u Scalable = &Rectangle{3, 4} // fine
The error message is worth recognising, because it names the cause:
cannot use Rectangle{…} (value of type Rectangle) as Scalable value:
Rectangle does not implement Scalable (method Scale has pointer receiver)
The practical rule: pick one receiver kind per type and use it throughout. If any method needs a pointer receiver, make them all pointer receivers: otherwise the type satisfies some interfaces by value and others only by pointer, and every caller has to know which.
The standard library follows this. time.Time is small and immutable, so every method takes a value
receiver. strings.Builder accumulates state, so every method takes a pointer.
Choosing
Use a pointer receiver when:
- the method modifies the receiver
- the struct is large enough that copying costs
- any other method on the type needs one (consistency)
- the type contains a
sync.Mutexor similar. Copying a mutex copies its state, which is a bug the race detector will find andgo vetwarns about
Use a value receiver when:
- the type is small and treated as a value: a
Point, atime.Duration - the method must work on a copy by design
- the type is a map, slice, channel or function type
A nil pointer receiver is legal and occasionally useful, since the method is dispatched statically:
type List struct {
Value int
Next *List
}
func (l *List) Len() int {
if l == nil { // valid: methods can be called on nil pointers
return 0
}
return 1 + l.Next.Len()
}
var empty *List
fmt.Println(empty.Len()) // 0, no panic
That works because a method on a pointer receiver is a function taking the pointer. Nothing is dereferenced until you dereference it.
Methods on any local type
Methods are not restricted to structs. Any type defined in the same package can have them, which is how Go adds behaviour to primitives:
type Celsius float64
func (c Celsius) Fahrenheit() Celsius {
return c*9/5 + 32
}
func (c Celsius) String() string {
return fmt.Sprintf("%.1f°C", float64(c))
}
fmt.Println(Celsius(21).Fahrenheit()) // 69.8°C
fmt.Println(Celsius(21)) // 21.0°C — String() is used by fmt
type Stack []int
func (s *Stack) Push(v int) { *s = append(*s, v) }
func (s *Stack) Pop() (int, bool) {
if len(*s) == 0 {
return 0, false
}
old := *s
v := old[len(old)-1]
*s = old[:len(old)-1]
return v, true
}
The *Stack receiver is required here because Push changes the slice’s length, which means
reassigning the slice header, a value receiver would only change the copy.
You cannot define a method on a type from another package. func (t time.Time) Foo() does not
compile. Define your own type instead:
type Timestamp time.Time
func (t Timestamp) IsBusinessDay() bool {
d := time.Time(t).Weekday()
return d != time.Saturday && d != time.Sunday
}
Note that a defined type does not inherit the original’s methods, Timestamp has none of
time.Time’s. Embed it instead if you want them:
type Timestamp struct {
time.Time // embedded: Format, Add, Sub and the rest are promoted
}
Promotion through embedding
An embedded type’s methods are promoted to the outer type:
type Base struct {
ID int64
}
func (b Base) Describe() string { return fmt.Sprintf("#%d", b.ID) }
type User struct {
Base
Email string
}
u := User{Base{1}, "[email protected]"}
fmt.Println(u.Describe()) // "#1" — promoted from Base
This is composition, not inheritance: User is not a Base, and a function taking Base will not
accept a User. What promotion does give you is interface satisfaction through the embedded type —
if Base implements an interface, so does User.
Shadowing works as you would expect: a method on the outer type takes precedence, and the embedded one is still reachable explicitly:
func (u User) Describe() string { return u.Email }
u.Describe() // "[email protected]"
u.Base.Describe() // "#1"
Note that a promoted method’s receiver is still the embedded value. A Base method cannot see
User.Email, and there is no virtual dispatch, Go has no overriding.
Method values and expressions
A method can be taken as a function value with its receiver bound:
r := Rectangle{3, 4}
area := r.Area // method value: r is captured now
r.Width = 10
fmt.Println(area()) // 12 — the ORIGINAL r was copied at binding time
That capture is worth knowing. With a value receiver the receiver is copied when the method value is created, so later changes are invisible. With a pointer receiver the pointer is captured, so changes are visible.
A method expression leaves the receiver as the first parameter:
areaOf := Rectangle.Area // func(Rectangle) float64
fmt.Println(areaOf(r))
scale := (*Rectangle).Scale // func(*Rectangle, float64)
scale(&r, 2)
Method values are what make defer wg.Done() and go worker.Run() work: both bind the receiver at
the point of the statement.
Frequently asked questions
What is the difference between a value and a pointer receiver?
A value receiver gets a copy and cannot modify the original; a pointer receiver can. The choice also determines which method set the method belongs to.
Why does my type not satisfy an interface?
One of the interface’s methods has a pointer receiver,
so it belongs to *T’s method set rather than T’s. Pass &value.
Should I mix value and pointer receivers on one type?
No. Pick one and use it throughout, otherwise the type satisfies some interfaces by value and others only by pointer.
Do I need to write (&r).Scale()?
No. Go inserts the address-of automatically for addressable values. It cannot for map entries or function results, which have no address.
Why can I not call a pointer method on a map entry?
Map entries are not addressable, because map growth relocates them. Store pointers as the map’s value type.
Can a method be called on a nil pointer?
Yes. Dispatch is static, so the method runs with a nil receiver and can check for it, which is how nil-safe linked structures are written.
Can I add a method to a type from another package?
No. Define your own type based on it, or embed it. A defined type does not inherit the original’s methods; embedding promotes them.
Is embedding inheritance?
No. Methods and fields are promoted, but there is no subtype relationship and no overriding, a promoted method’s receiver is the embedded value and cannot see the outer type.
When does a method value capture its receiver?
At the point the method value is created. With a value receiver that is a copy, so later mutations of the original are not visible.
Should a type containing a mutex use pointer receivers?
Yes, always. Copying a struct copies the
mutex and its state, which breaks mutual exclusion. go vet warns about it.