Method Syntax
Understand the syntax for declaring methods in Go, from the receiver parameter to binding a method to its base type.
A method in Go is a function that carries an extra parameter — the receiver — placed between the func keyword and the method name. That single parameter is what turns an ordinary function into a method and what ties the method to a specific type. If you have written functions in Go, methods will feel like a small, natural extension.
Method Declaration Syntax
A method declaration follows this structure:
func (receiver) MethodName(parameters) ReturnType {
// body
}
A concrete example with a Book type:
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
The receiver is (b Book). Everything after it — Pages() int — is a standard function signature. The receiver acts like a regular parameter, but it appears before the method name and dictates which type the method belongs to.
The Go specification formalises this as:
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
Receiver = Parameters .
A receiver is syntactically identical to a parameter list, but it must contain exactly one parameter — no more, no less. That single parameter’s type is called the receiver type. The underlying type of the receiver (stripping any pointer indirection) is the receiver base type, and the method is said to be bound to that base type.
Methods vs. Functions:
A method is just a function with a receiver. If you take the receiver away, you get a plain function. This means methods are not a separate language construct; they are declared with the same func keyword and follow the same scoping and visibility rules.
The Receiver Parameter
The receiver is the single most distinctive part of method syntax. Consider this method on a Player type:
type Player struct {
name string
score int
}
func (p Player) DisplayName() string {
return p.name
}
The identifier p is the receiver name. Inside the method body, p is a local variable holding a copy of the value the method was called on. You can name it anything an identifier allows, but by convention Go uses a short name — often the first letter of the receiver type in lowercase. That convention eliminates repetitive this. or self. prefixes and keeps the code clean.
A receiver type can be the type itself or a pointer to it:
// Value receiver
func (p Player) DisplayName() string { ... }
// Pointer receiver
func (p *Player) SetScore(s int) { ... }
Both forms are syntactically identical apart from the *. The syntax, however, is always the same: func followed by the receiver in parentheses, then the method name.
Receiver name omission:
You must give the receiver a name in the declaration, even if you never use it inside the method body. Using _ as the name is allowed and explicitly signals that the receiver is unused, but it is rarely needed.
Binding a Method to Its Base Type
When you write a method declaration, you are doing two things at once: declaring the method itself and associating it with the receiver’s base type. The compiler treats the method as part of that type’s identity.
Consider the Book type again:
type Book struct {
pages int
}
func (b Book) Pages() int {
return b.pages
}
Pages is now bound to Book. In practice this means you call it on a value of type Book:
b := Book{pages: 320}
fmt.Println(b.Pages()) // 320
The compiler also creates an implicit function that has the same name but with the receiver moved into the argument list as the first parameter:
func Book.Pages(b Book) int {
return b.pages
}
You can even call that implicit function directly, though this is rare outside of certain patterns:
fmt.Println(Book.Pages(b)) // 320
This implicit function is what makes method expressions work, but for day-to-day code you will use the value.Method() form.
Checkpoint:
If you declare a method and can call it with value.MethodName(), and you also see the compiler recognises Type.MethodName as a valid function reference, the binding is working correctly.
Allowed Receiver Types
Not every type can have methods. The Go spec places four requirements on the receiver base type T:
Tmust be a defined type. That means you must create it with atypedeclaration (type MyInt int,type Book struct{...}, etc.). Predeclared types likeint,string, or[]bytecannot have methods because they are not defined types — they come from the language itself.Tmust be defined in the same package as the method. You cannot add methods to a type imported from another package, not even to standard library types. This is a deliberate design choice that keeps types predictable.Tmust not be a pointer type. You cannot writefunc (p *MyInt) ...whereMyIntis itself defined as a pointer type. The receiver type can be a pointer to a non-pointer base type, but the base type itself cannot be a pointer.Tmust not be an interface type. Methods are declared on concrete types. Interface types describe method sets that concrete types must satisfy; they themselves are not concrete and cannot have method implementations.
Examples that illustrate the rules:
// Allowed: defined non-pointer type in same package
type Counter int
func (c Counter) Value() int { return int(c) }
// Allowed: struct type
type Employee struct { Name string }
func (e Employee) Name() string { return e.Name }
// Allowed: pointer receiver on non-pointer base type
func (e *Employee) SetName(name string) { e.Name = name }
// Not allowed: base type is a pointer
type Ptr *int
// func (p Ptr) Something() {} // compile error
// Not allowed: type defined in another package
// func (s string) Len() int {} // compile error
// Not allowed: interface type
// type Reader interface { Read([]byte) (int, error) }
// func (r Reader) DoSomething() {} // compile error
Receiver not in local package:
Attempting to declare a method on a built-in type like func (s string) Len() int will produce a compile error: "cannot define new methods on non-local type string." You can work around this by defining a new named type based on the built-in type within your package, and then declaring methods on that.
Implicit Method for Pointer Types
When you declare a method with a value receiver on a type T, the compiler automatically makes an equivalent method available on *T. That implicit method simply dereferences the pointer and calls the original value-receiver method.
Given this declaration:
func (b Book) Pages() int {
return b.pages
}
The compiler effectively adds:
func (b *Book) Pages() int {
return b.Pages() // same as (*(*Book)).Pages()
}
This means you can call Pages on a *Book directly:
b := &Book{pages: 200}
fmt.Println(b.Pages()) // works, calls implicit method
This rule is part of method syntax because it explains why a method declared with a value receiver is usable through a pointer without any extra code. The reverse — a pointer receiver method implicitly working on a value — is not true because a value does not have an address that the pointer receiver would expect.
Common Mistakes When Declaring Methods
- Forgetting the receiver parentheses. The receiver must be enclosed in parentheses, even if it is a single identifier with no pointer notation:
func (r Rectangle) Area()— omitting the parentheses is a syntax error. - Using a type from another package as the receiver base type. If you need methods on a type you didn’t define, you can embed it in a local struct or create a new defined type based on it.
- Declaring a method on an interface. Methods belong to concrete types. If you want to provide an implementation of an interface, you declare methods on a concrete type that satisfies the interface.
- Assuming the receiver name must be
thisorself. Go style prefers a short abbreviation of the type name. Usingthisis not idiomatic and will be flagged by linting tools.
Unused receiver:
If you declare a method with a receiver you never use, you must still give it a name or use _. However, a method that ignores its receiver is often a sign that it should be a plain function instead.
Summary
Method syntax in Go is essentially a function signature with a receiver parameter placed in front of the method name. That receiver binds the method to its base type, and the binding works only when the base type is a defined type in the current package, is not a pointer type, and is not an interface. The compiler further extends the binding by adding an implicit method to the pointer type for every value-receiver method.
A method declaration is a single statement that tells the compiler three things: the name of the method, its signature, and which type it belongs to. Understanding that structure is the foundation for value vs pointer receivers, method sets, and interfaces — all of which build directly on this syntax.