It is not recommended to use pointer and value receivers mixed together, and I have always followed that rule. Now I have a case where it feels like it is almost needed to mix, otherwise you’ll get ugly code in my opinion:
Example 1: Mixed
type bitBoard uint64
// Here we need a pointer receiver, since we are changing the value
func (b *bitBoard) set(pos int) {
*b |= bitBoard(uint64(1) << uint(pos))
}
// Value receiver, we don't change the value
func (b bitBoard) String() string {
result := strings.Repeat("0", 64) + fmt.Sprintf("%b", b)
return result[len(result)-64:]
}
func main() {
b1 := bitBoard(12)
b1.set(8)
b2 := bitBoard(10)
// Works with mixed pointer and value receivers
fmt.Println((b2 & b1).String())
}
In this case, the pointer receiver is needed for the set() method, but not for String().
If I try to follow the “stop-mixing-pointer-and-value-receiver”-rule, I need to do it like this instead:
type bitBoard uint64
// Here we need a pointer receiver, since we are changing the value
func (b *bitBoard) set(pos int) {
*b |= bitBoard(uint64(1) << uint(pos))
}
// Pointer receiver, even if we don't change the value
func (b *bitBoard) String() string {
result := strings.Repeat("0", 64) + fmt.Sprintf("%b", *b)
return result[len(result)-64:]
}
func main() {
b1 := bitBoard(12)
b1.set(8)
b2 := bitBoard(10)
// Using a pointer receiver, we now need to create a temporary variable
b3 := b2 & b1
fmt.Println(b3.String())
}
This to me is kind of ugly, having to create a temporary variable to store the bitBoard in, before printing it. In my case, I have 10+ bitBoards that needs printing, so that’s a lot of extra variables.
This happens of course because I want to perform bit operations on two bitboards before printing them (p & b).
Is there a better way to do this, so that I don’t need to mix pointer and value receivers, and so that I don’t have to create a temporary variable? Or should I just accept that the “no-mixing”-rule is not really a strict rule, and move on using mixed in cases like above?