3
type EventPrefs struct {
    Call          bool
    Presence      bool 
    Endpoint      bool
    VoiceMail     bool
    CallRecording bool
}

Currently, the size of that struct type is 5 bytes but I would like to use bits. Is there any way to do that?

turivishal
  • 34,368
  • 7
  • 36
  • 59
  • 13
    You cannot, there are no bitfields in Go. You can do `type event uint8` and do the bitfiddling yourself. – Volker Jun 22 '21 at 11:09
  • 5
    Have you gathered empirical evidence that the memory footprint of your struct type actually is a problem? How about making its fields unexported now so you can be free to change its implementation later? – jub0bs Jun 22 '21 at 11:29
  • 3
    Bits are not individually addressable at the hardware level, which is why you have to use bitmasking like in any other language. – JimB Jun 22 '21 at 12:30

1 Answers1

13

There is no "bit" type in Go, so if you want to pack multiple bool information into bits, you have to implement it yourself. Declare a field of type uint8 (or uint16 or any other integer type), and provide methods that get / set specific bits of the field.

General bit setting / clearing is as simple as this:

var masks = []uint8{0x01, 0x02, 0x04, 0x08, 0x10}

func set(field, data uint8, b bool) uint8 {
    if b {
        return data | masks[field] // Set bit
    }
    return data ^ masks[field] // Clear bit
}

func get(field, data uint8) bool {
    return data&masks[field] != 0
}

Packing your 5 bool fields into an uint8 value:

type EventPrefs struct {
    data uint8
}

func (e *EventPrefs) SetCall(b bool) { e.data = set(0, e.data, b) }
func (e *EventPrefs) Call() bool     { return get(0, e.data) }

func (e *EventPrefs) SetPresence(b bool) { e.data = set(1, e.data, b) }
func (e *EventPrefs) Presence() bool     { return get(1, e.data) }

func (e *EventPrefs) SetEndpoint(b bool) { e.data = set(2, e.data, b) }
func (e *EventPrefs) Endpoint() bool     { return get(2, e.data) }

func (e *EventPrefs) SetVoiceMail(b bool) { e.data = set(3, e.data, b) }
func (e *EventPrefs) VoiceMail() bool     { return get(3, e.data) }

func (e *EventPrefs) SetCallRecording(b bool) { e.data = set(4, e.data, b) }
func (e *EventPrefs) CallRecording() bool     { return get(4, e.data) }

Testing it:

ep := &EventPrefs{}

fmt.Println("Calls:", ep.Call(), ep.data)
ep.SetCall(true)
fmt.Println("Calls:", ep.Call(), ep.data)

fmt.Println("Presence:", ep.Presence(), ep.data)
ep.SetPresence(true)
fmt.Println("Presence:", ep.Presence(), ep.data)
ep.SetPresence(false)
fmt.Println("Presence:", ep.Presence(), ep.data)

Which outputs (try it on the Go Playground):

Calls: false 0
Calls: true 1
Presence: false 1
Presence: true 3
Presence: false 1

Is saving 4 bytes worth the hassle? Rarely.

Note: the above solution can have many variations. For example the masks can be "computed" using bitshifts, the set() and get() functions could be methods of EventPrefs and so the data parameter would not be needed (and set() could directly set the EventPrefs.data field so no return value would be needed either). If set() remains a function, the data param could be a pointer so set() could change the pointed value without returning the new data etc. The data field may have its own declared type e.g. bitpack with get() and set() methods attached to it.

See related: Difference between some operators "|", "^", "&", "&^". Golang

icza
  • 389,944
  • 63
  • 907
  • 827