T
and *T
, where T
must satisfy 4 conditions:
T
must be a defined type;
T
must be defined in the same package as the method declaration;
T
must not be a pointer type;
T
must not be an interface type. Interface types will be explained in the next article.
T
and *T
are called the receiver type of the respective methods declared for them. Type T
is called the receiver base types of all methods declared for both type T
and *T
.
T
and *T
types specified above. The effect is the same as declaring methods for the T
and *T
types themselves.
int
and string
, for we can't declare methods in the builtin
standard package.
*T
which are described above.
()
and declared between the func
keyword and the method name.
// Age and int are two distinct types. We
// can't declare methods for int and *int,
// but can for Age and *Age.
type Age int
func (age Age) LargerThan(a Age) bool {
return age > a
}
func (age *Age) Increase() {
*age++
}
// Receiver of custom defined function type.
type FilterFunc func(in int) bool
func (ff FilterFunc) Filte(in int) bool {
return ff(in)
}
// Receiver of custom defined map type.
type StringSet map[string]struct{}
func (ss StringSet) Has(key string) bool {
_, present := ss[key]
return present
}
func (ss StringSet) Add(key string) {
ss[key] = struct{}{}
}
func (ss StringSet) Remove(key string) {
delete(ss, key)
}
// Receiver of custom defined struct type.
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
func (b *Book) SetPages(pages int) {
b.pages = pages
}
this
, which is not a recommended identifier for receiver parameter names in Go.
*T
is called pointer receiver, non-pointer receivers are called value receivers. Personally, I don't recommend to view the terminology pointer as an opposite of the terminology value, because pointer values are just special values. But, I am not against using the pointer receiver and value receiver terminologies here. The reason will be explained below.
_
. A type can have multiple methods with the blank identifier as name. But such methods can never be called. Only exported methods can be called from other packages. Method calls will be introduced in a later section.
Book
and type *Book
in the last example in the last section, two following functions are implicitly declared by compiler:
func Book.Pages(b Book) int {
// The body is the same as the Pages method.
return b.pages
}
func (*Book).SetPages(b *Book, pages int) {
// The body is the same as the SetPages method.
b.pages = pages
}
Book.Pages
and (*Book).SetPages
, are both of the form TypeDenotation.MethodName
. As identifiers in Go can't contain the period special characters, the two implicit function names are not legal identifiers, so the two functions can't be declared explicitly. They can only be declared by compilers implicitly, but they can be called in user code:
package main
import "fmt"
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
func (b *Book) SetPages(pages int) {
b.pages = pages
}
func main() {
var book Book
// Call the two implicit declared functions.
(*Book).SetPages(&book, 123)
fmt.Println(Book.Pages(book)) // 123
}
func (b Book) Pages() int {
return Book.Pages(b)
}
func (b *Book) SetPages(pages int) {
(*Book).SetPages(b, pages)
}
T
, a corresponding method with the same name will be implicitly declared by compiler for type *T
. By the example above, the Pages
method is declared for type Book
, so a method with the same name Pages
is implicitly declared for type *Book
:
// Note: this is not a legal Go syntax.
// It is shown here just for explanation purpose.
// It indicates that the expression (&aBook).Pages
// is evaluated as aBook.Pages (see below sections).
func (b *Book) Pages = (*b).Pages
func (*Book).Pages(b *Book) int {
return Book.Pages(*b)
}
func
keyword. We can view each method declaration is composed of the func
keyword, a receiver parameter declaration, a method specification and a method (function) body.
Pages
and SetPages
methods shown above are
Pages() int
SetPages(pages int)
_
. Interface types will be explained in the next article.
Book
type shown in the previous sections is
Pages() int
*Book
type is
Pages() int
SetPages(pages int)
T
, assume it is neither a pointer type nor an interface type, for the reason mentioned in the last section, the method set of a type T
is always a subset of the method set of type *T
. For example, the method set of the Book
type shown above is a subset of the method set of the *Book
type.
v
, its method m
can be represented with the selector form v.m
, which is a function value.
package main
import "fmt"
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
func (b *Book) SetPages(pages int) {
b.pages = pages
}
func main() {
var book Book
fmt.Printf("%T \n", book.Pages) // func() int
fmt.Printf("%T \n", (&book).SetPages) // func(int)
// &book has an implicit method.
fmt.Printf("%T \n", (&book).Pages) // func() int
// Call the three methods.
(&book).SetPages(123)
book.SetPages(123) // equivalent to the last line
fmt.Println(book.Pages()) // 123
fmt.Println((&book).Pages()) // 123
}
->
operator in Go to call methods with pointer receivers, so (&book)->SetPages(123)
is illegal in Go.)
book.SetPages(123)
in the above example compile okay? After all, the method SetPages
is not declared for the Book
type. On one hand, this can be viewed as a syntactic sugar to make programming convenient. This sugar only works for addressable value receivers. Compiler will implicitly take the address of the addressable value book
when it is passed as the receiver argument of a SetPages
method call. On the other hand, we should also think aBookExpression.SetPages
is always a legal selector (from the syntax view), even if the expression aBookExpression
is evaluated as an unaddressable Book
value, for which case, the selector aBookExpression.SetPages
is invalid (but legal).
nil
.
package main
type StringSet map[string]struct{}
func (ss StringSet) Has(key string) bool {
// Never panic here, even if ss is nil.
_, present := ss[key]
return present
}
type Age int
func (age *Age) IsNil() bool {
return age == nil
}
func (age *Age) Increase() {
*age++ // If age is a nil pointer, then
// dereferencing it will panic.
}
func main() {
_ = (StringSet(nil)).Has // will not panic
_ = ((*Age)(nil)).IsNil // will not panic
_ = ((*Age)(nil)).Increase // will not panic
_ = (StringSet(nil)).Has("key") // will not panic
_ = ((*Age)(nil)).IsNil() // will not panic
// This following line will panic. But the
// panic is not caused by invoking the method.
// It is caused by the nil pointer dereference
// within the method body.
((*Age)(nil)).Increase()
}
package main
import "fmt"
type Book struct {
pages int
}
func (b Book) SetPages(pages int) {
b.pages = pages
}
func main() {
var b Book
b.SetPages(123)
fmt.Println(b.pages) // 0
}
package main
import "fmt"
type Book struct {
pages int
}
type Books []Book
func (books Books) Modify() {
// Modifications on the underlying part of
// the receiver will be reflected to outside
// of the method.
books[0].pages = 500
// Modifications on the direct part of the
// receiver will not be reflected to outside
// of the method.
books = append(books, Book{789})
}
func main() {
var books = Books{{123}, {456}}
books.Modify()
fmt.Println(books) // [{500} {456}]
}
Modify
method are exchanged, then both of the modifications will not be reflected to outside of the method body.
func (books Books) Modify() {
books = append(books, Book{789})
books[0].pages = 500
}
func main() {
var books = Books{{123}, {456}}
books.Modify()
fmt.Println(books) // [{123} {456}]
}
append
call will allocate a new memory block to store the elements of the copy of the passed slice receiver argument. The allocation will not reflect to the passed slice receiver argument itself.
func (books *Books) Modify() {
*books = append(*books, Book{789})
(*books)[0].pages = 500
}
func main() {
var books = Books{{123}, {456}}
books.Modify()
fmt.Println(books) // [{500} {456} {789}]
}
v
is a value of type T
and v.m
is a legal method value expression,
m
is a method explicitly declared for type *T
, then compilers will normalize it as (&v).m
;
m
is a method explicitly declared for type T
, then the method value expression v.m
is already normalized.
p
is a value of type *T
and p.m
is a legal method value expression,
m
is a method explicitly declared for type T
, then compilers will normalize it as (*p).m
;
m
is a method explicitly declared for type *T
, then the method value expression p.m
is already normalized.
v.m
is a normalized method value expression, at run time, when the method value v.m
is evaluated, the receiver argument v
is evaluated and a copy of the evaluation result is saved and used in later calls to the method value.
b.Pages
is already normalized. At run time, a copy of the receiver argument b
is saved. The copy is the same as Book{pages: 123}
, the subsequent modification of value b
has no effects on this copy. That is why the call f1()
prints 123
.
p.Pages
is normalized as (*p).Pages
at compile time. At run time, the receiver argument *p
is evaluated to the current b
value, which is Book{pages: 123}
. A copy of the evaluation result is saved and used in later calls of the method value, that is why the call f2()
also prints 123
.
p.Pages2
is already normalized. At run time, a copy of the receiver argument p
is saved. The saved value is the address of the value b
, thus any changes to b
will be reflected through dereferencing of the saved value, that is why the call g1()
prints 789
.
b.Pages2
is normalized as (&b).Pages2
at compile time. At run time, a copy of the evaluation result of &b
is saved. The saved value is the address of the value b
, thus any changes to b
will be reflected through dereferencing of the saved value, that is why the call g2()
prints 789
.
package main
import "fmt"
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
func (b *Book) Pages2() int {
return (*b).Pages()
}
func main() {
var b = Book{pages: 123}
var p = &b
var f1 = b.Pages
var f2 = p.Pages
var g1 = p.Pages2
var g2 = b.Pages2
b.pages = 789
fmt.Println(f1()) // 123
fmt.Println(f2()) // 123
fmt.Println(g1()) // 789
fmt.Println(g2()) // 789
}
MyInt
, the defined type Age
has not an IsOdd
method.
package main
type MyInt int
func (mi MyInt) IsOdd() bool {
return mi%2 == 1
}
type Age MyInt
func main() {
var x MyInt = 3
_ = x.IsOdd() // okay
var y Age = 36
// _ = y.IsOdd() // error: y.IsOdd undefined
_ = y
}
sync
standard package should not be copied, so declaring methods with value receivers for struct types which embedding the types in the sync
standard package is problematic.
The Go 101 project is hosted on Github. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.
If you would like to learn some Go details and facts every serveral days, please follow Go 101's official Twitter account @zigo_101.
reflect
standard package.sync
standard package.sync/atomic
standard package.