ADDED:
* netx.IsPub
* encodingx/hexx

Rest are mostly small corrections and docs
This commit is contained in:
brent saner
2026-06-22 18:51:13 -04:00
parent c6fc692f5e
commit 58556d7281
35 changed files with 5492 additions and 2486 deletions
+117 -44
View File
@@ -11,21 +11,84 @@ However, a [bitmask.MaskBit] is backed by a uint which (depending on your platfo
"But wait, that takes up more memory though!"
Yep, but bitmasking lets you store a "boolean" AT EACH BIT - it operates on
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.
Which means on 32-bit platforms, a [MaskBit] can have up to 4294967295 "booleans" in a single value (0 to (2^32)-1).
In other words:
On 64-bit platforms, a [MaskBit] can have up to 18446744073709551615 "booleans" in a single value (0 to (2^64)-1).
type (
OptStruct struct {
A bool
B bool
C bool
D bool
E bool
F bool
G bool
H bool
}
)
If you tried to do that with Go bool values, that'd take up 4294967295 bytes (4 GiB)
or 18446744073709551615 bytes (16 EiB - yes, that's [exbibytes]) of RAM for 32-bit/64-bit platforms respectively.
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.
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 Doom used bitmasking for the "dmflags" value in its server configs.
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
@@ -34,6 +97,8 @@ To use this library, set constants like thus:
package main
import (
"fmt"
"r00t2.io/goutils/bitmask"
)
@@ -45,34 +110,32 @@ To use this library, set constants like thus:
// ...
)
var MyMask *bitmask.MaskBit
var (
MyMask *bitmask.MaskBit = bitmask.NewMaskBit()
)
func main() {
MyMask = bitmask.NewMaskBit()
MyMask.AddFlag(OPT1)
MyMask.AddFlag(OPT3)
_ = MyMask
// 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))
}
This would return true:
MyMask.HasFlag(OPT1)
As would this:
MyMask.HasFlag(OPT3)
But this would return false:
MyMask.HasFlag(OPT2)
# Technical Caveats
TARGETING
Targeting
When implementing, you should always set a "source" mask (e.g. MyMask, from Usage section above)
as the actual value.
When implementing, you should always set 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
@@ -88,7 +151,7 @@ and your library has the following permission bits defined:
PermsAdmin // 16
)
And you want to see if the user has the PermsRead flag set, you would do:
and you want to see if the user has the PermsRead flag set, you would do:
userPermMask = bitmask.NewMaskBitExplicit(userPerms)
if userPermMask.HasFlag(PermsRead) {
@@ -98,55 +161,65 @@ And you want to see if the user has the PermsRead flag set, you would do:
NOT:
userPermMask = bitmask.NewMaskBitExplicit(PermsRead)
// Nor:
// userPermMask = PermsRead
if userPermMask.HasFlag(userPerms) {
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.
COMPOSITES
Remember, [MaskBit.HasFlag] (and other methods) are for comparing/modifying an
*authoritative/concrete value* with one or more *attributes*, not the other way around.
If you want to define a set of flags that are a combination of other flags,
your inclination would be to bitwise-OR them together:
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 dandy. But if you then have:
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]:
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 will only return true if you then ALSO do:
// This would require setting flagA first.
// The order of setting flagA/flagB doesn't matter,
// but you must have both set for HasFlag(flagAB) to return true.
myMask.AddFlag(flagB)
It would only return true if you did:
or if you do:
// This can be done with or without additionally setting flagA.
// ...
myMask.AddFlag(flagAB)
Instead, if you want to see if a mask has membership within a composite flag,
you can use [MaskBit.IsOneOf].
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
@@ -157,6 +230,6 @@ you may be interested in one of the following libraries:
* [github.com/abice/go-enum]
* [github.com/jeffreyrichter/enum/enum]
[exbibytes]: https://simple.wikipedia.org/wiki/Exbibyte
[dmflags]: https://doomwiki.org/wiki/DMFlags
*/
package bitmask