banner
魔法少女秋小日

Qiuxiaori

github
email

Go Language Type System

Value Receivers and Pointer Receivers#

Functions with specified receivers are called methods. The receiver can be a value receiver Animal or a pointer receiver *Animal:

type Animal struct {
	name string
}

func (a Animal) valueFn() {} // value receiver
func (a *Animal) ptrFn() {} // pointer receiver

When calling a method, the caller can be either a value type or a pointer type of that type.

  • With a value receiver, regardless of whether the caller is a value type or a pointer type, a copy of the caller is always passed, and the method does not modify the caller itself:
// eg1.
func (a Animal) valueFn() {
	a.name = "new_" + a.name
}
func main() {
	a := Animal{"cat"} // value type
	a.valueFn()
	println("this is name", a.name)
	// => this is name cat
	
	b := &Animal{"dog"} // pointer type
	b.valueFn()
	println("this is name", b.name)
	// => this is name dog
}
  • With a pointer receiver, a reference to the caller is passed, so the caller can be modified:
// eg2.
func (a *Animal) ptrFn() {
	a.name = "new_" + a.name
}
func main() {
	a := Animal{"cat"} // value type
	a.ptrFn()
	println("this is name", a.name)
	// => this is name new_cat
	
	b := &Animal{"dog"} // pointer type
	b.ptrFn()
	println("this is name", b.name)
	// => this is name new_dog
}

This is achieved by the compiler doing some work, as shown in the table below:

-Value ReceiverPointer Receiver
Value Type CallerThe method uses a copy of the caller, similar to "pass by value"The method is called using a reference to the value, eg2 is actually (&a).ptrFn()
Pointer Type CallerThe pointer is dereferenced to a value, eg1 is actually (*b).valueFn()It is also "pass by value" in practice, the operations in the method will affect the caller, similar to passing a pointer, a copy of the pointer is made

The Essence of Types#

After declaring a new type and before declaring a method for that type, you need to answer a question: what is the essence of this type? If you add or remove a value from this type, do you create a new value or modify the current value? If you create a new value, the methods of this type should use value receivers. If you modify the current value, use pointer receivers. This answer will also affect how values of this type are passed within the program: whether they are passed by value or by pointer. It is important to maintain consistency in passing values. The underlying principle behind this is not to focus on how a method handles a value, but on the essence of the value itself.

The decision to use a value receiver or a pointer receiver should not be based on whether the method modifies the received value. This decision should be based on the essence of the type. An exception to this rule is when you need the value of a type to comply with an interface. Even if the essence of the type is non-primitive, you can still choose to declare methods using value receivers. This approach fully complies with the mechanism of calling methods on interface values.

  • "Go in Action 5.3"
  • Built-in types: numeric types, string types, and boolean types. These types are essentially primitive types. Therefore, when adding or removing values from these types, a new value is created. Based on this conclusion, when passing values of these types to methods or functions, a copy of the corresponding value should be passed.
  • Primitive types: slice, map, channel, interface, and function types. I didn't understand the rest 😅
  • Struct types: used to describe a group of values. Follow the rule of passing by pointer if modification is needed. If the value of a type has a non-primitive essence, it should be shared rather than copied. Even if the method does not modify the receiver, it should still be declared with a pointer receiver.

I didn't understand this section very well, but it seems to be saying that for non-struct types, the convention of the standard library should be followed; for struct types, in general, if modification of the caller is not needed, pass by value, and if modification is needed, pass by pointer.

Interfaces#

Although methods with both value receivers and pointer receivers can be called on different types of callers, there are slight differences in implementing interfaces. Implementing a method with a value receiver implicitly implements the method with a pointer receiver, while implementing a method with a pointer receiver does not automatically implement the method with a value receiver.

// eg3. Compiles successfully
type IAnimal interface {
	valueFn()
	ptrFn()
}

func (a Animal) valueFn() {}
func (a *Animal) ptrFn() {}

func main() {
	var a IAnimal = &Animal{"cat"}
	a.valueFn()
	a.ptrFn()
}
// eg4. Compilation fails
func main() {
	var a IAnimal = Animal{"cat"} // changed to value type
	a.valueFn()
	a.ptrFn()
}
// => .\main.go:22:18: cannot use Animal{…} (value of type Animal) as IAnimal value in variable declaration: Animal does not implement IAnimal (method ptrFn has pointer receiver)

Method Sets#

To understand why the value type Animal cannot implement the interface IAnimal when implementing it with a pointer receiver, we need to understand method sets. A method set defines a set of methods associated with a given type of value or pointer. The type of receiver used when defining a method determines whether the method is associated with a value, a pointer, or both.

  • Method set specification:
valuesmethods receivers
T(t T)
* T(t T) and (t * T)

This means that the method set of a value of type T only includes methods declared with a value receiver. The method set of a pointer to type T includes methods declared with both value and pointer receivers.

  • From the perspective of the receiver, the method set specification is as follows:
methods receiversvalues
(t T)T and * T
(t * T)* T

This perspective means that if a value receiver is used to implement an interface, both value type values and pointer type values can implement the corresponding interface. If a pointer receiver is used to implement an interface, only pointers to that type can implement the corresponding interface.

Therefore, in eg4, the value type Animal will prompt that it does not implement the interface IAnimal.

Embedded Types#

Embedded types are existing types declared directly in a new struct type. The embedded type is referred to as the inner type of the new outer type.

type Cat struct {
	Animal
	color string
}

func main() {
	c := Cat{Animal{"miao"},"yellow"}
	c.Animal.valueFn()
}

The properties and methods of the inner type can also be promoted to the outer type for direct access:

func main() {
	//...
	c.valueFn()
	println("this is c name", c.name)
}

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.