Files
go_goutils/bitmask/doc.go
T
brent saner 58556d7281 v1.16.9
ADDED:
* netx.IsPub
* encodingx/hexx

Rest are mostly small corrections and docs
2026-06-22 18:51:13 -04:00

236 lines
5.8 KiB
Go

/*
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