gostructbitmemory-footprint

How to use bit instead of bool in golang structure?


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?


Solution

  • 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