/* Package bitmask handles a flag-like opt/bitmask system. See https://yourbasic.org/golang/bitmask-flag-set-clear/ for basic information on what bitmasks are and why they're useful. Specifically, in the case of Go, they allow you to essentially manage many, many, many "booleans" as part of a single value. A single bool value in Go takes up 8 bits/1 byte, unavoidably. However, a [bitmask.MaskBit] is backed by a uint which (depending on your platform) is either 32 bits/4 bytes or 64 bits/8 bytes. "But wait, that takes up more memory though!" Yep - compared to a *single boolean*. But bitmasking lets you store a "boolean" AT EACH BIT boundary - it operates on whether a bit in a byte/set of bytes at a given position is 0 or 1. In other words: type ( OptStruct struct { A bool B bool C bool D bool E bool F bool G bool H bool } ) One instance of OptStruct takes up *8 bytes* (*64 bits*). As a uint8 bitmask, however, it takes up exactly *one byte/8 bits*: type ( Opt uint8 ) const OptNone Opt = 0 const ( A Opt = 1 << iota // 1 B // 2 C // 4 D // 8 E // 16 F // 32 G // 64 H // 128 // Would overflow to 0: // I ) As shown, the size of an unsigned integer determines the number of bit-boundaried flags for that bitmask. Which means on 32-bit platforms, a [MaskBit] can have up to 32 different flags but only occupies 4 bytes of memory. On 64-bit platforms, a [MaskBit] can have up to 64 different flags but only occupies 8 bytes of memory. If you tried to do that with a struct of booleans instead, that'd occupy 32 *bytes* and 64 *bytes* respectively. In summary, a bitmask set occupies 1/8th the amount of memory as a set of equal number of booleans in Go. You can, of course, extend this even further by defining meaningful "composites", or OR'd-combination of flags (see the "Composite Flags" section below): type Perms uint8 const NoPerms Perms = 0 const ( PermsRead Perms = 1 << iota PermsList PermsUpdate PermsCreate PermsDelete PermsUpdateBulk PermsCreateBulk PermsDeleteBulk // This makes it easy to add multiple permissions at once. PermsCRUD Perms = PermsCreate | PermsRead | PermsUpdate | PermsDelete PermsCRUDL Perms = PermsCRUD | PermsList // And so forth. ) "But that has to be so slow to unpack that!" Nope. It's not using compression or anything, the CPU is just comparing bit "A" vs. bit "B" 32/64 times. That's super easy work for a CPU. There's a reason ZDoom used bitmasking for the [dmflags] value in its server configs, and why *NIX platforms uses them for permission/type modes. # Usage To use this library, set constants like thus: package main import ( "fmt" "r00t2.io/goutils/bitmask" ) const OPTNONE bitmask.MaskBit = 0 const ( OPT1 bitmask.MaskBit = 1 << iota OPT2 OPT3 // ... ) var ( MyMask *bitmask.MaskBit = bitmask.NewMaskBit() ) func main() { MyMask.AddFlag(OPT1) MyMask.AddFlag(OPT3) // This would print true. fmt.Println(MyMask.HasFlag(OPT1)) // As would this: fmt.Println(MyMask.HasFlag(OPT3)) // But this would print false: fmt.Println(MyMask.HasFlag(OPT2)) } # Technical Caveats Targeting When implementing, you should always set a "source" mask (e.g. MyMask, from Usage section above) as the actual value. For example, if you are checking a permissions set for a user that has the value, say, 6 var userPerms uint = 6 // 0x0000000000000006 and your library has the following permission bits defined: const PermsNone bitmask.MaskBit = 0 const ( PermsList bitmask.MaskBit = 1 << iota // 1 PermsRead // 2 PermsWrite // 4 PermsExec // 8 PermsAdmin // 16 ) and you want to see if the user has the PermsRead flag set, you would do: userPermMask = bitmask.NewMaskBitExplicit(userPerms) if userPermMask.HasFlag(PermsRead) { // ... } NOT: userPermMask = bitmask.NewMaskBitExplicit(PermsRead) userPermMask.HasFlag(bitmask.MaskBit(userPerms)) NOR: userPermMask = PermsRead if userPermMask.HasFlag(bitmask.MaskBit(userPerms)) { // ... } This will be terribly, horribly wrong, cause incredibly unexpected results, and quite possibly cause massive security issues. Don't do it. Remember, [MaskBit.HasFlag] (and other methods) are for comparing/modifying an *authoritative/concrete value* with one or more *attributes*, not the other way around. Composite Flags If you want to define a set of flags that are a combination (a "composite", or OR'd set) of other flags, your inclination would be to bitwise-OR them together: const ( flagA bitmask.MaskBit = 1 << iota // 1 flagB // 2 flagC // 4 // ... ) const ( flagAB bitmask.MaskBit = flagA | flagB // 3 ) Which is fine, and the correct approach. But if you then have: var myMask *bitmask.MaskBit = bitmask.NewMaskBit() myMask.AddFlag(flagA) you may expect this call to [MaskBit.HasFlag]: myMask.HasFlag(flagAB) to be true, since flagA is "in" flagAB. It will return false - HasFlag does strict comparisons. It would only return true if you did: // ... myMask.AddFlag(flagAB) or: // ... myMask.AddFlag(flagA) myMask.AddFlag(flagB) Instead, if you want to see if a mask has *at least one flag of* a composite flag, you can use [MaskBit.HasOneOf]. # Other Options If you need something with more flexibility (as always, at the cost of complexity), you may be interested in one of the following libraries: * [github.com/alvaroloes/enumer] * [github.com/abice/go-enum] * [github.com/jeffreyrichter/enum/enum] [dmflags]: https://doomwiki.org/wiki/DMFlags */ package bitmask