Mixing pointer and value receivers needed?

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?

Sorry for the format of the code but maybe you can do something like this?

package main

import (
	"fmt"
)

type bitBoard struct {
	b uint64
}

func (b *bitBoard) set(pos int) {
	b.b |= 1 << pos
}

func (b *bitBoard) String() string {
	return fmt.Sprintf("%064b", b.b)
}

func (b *bitBoard) And(o *bitBoard) *bitBoard {
	return &bitBoard{b.b & o.b}
}

func Newbb(val uint64) *bitBoard {
	return &bitBoard{val}
}

func main() {
	b1 := Newbb(12)
	b1.set(8)
	b2 := Newbb(10)
	b3 := b2.And(b1)
	fmt.Println(b3.String())
	fmt.Println(b1.And(b2).String())
}

1 Like

Of course, why didn’t I think of that? :slight_smile:

Thank you…