-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Description
I've got a trie structure that works over 4 byte arrays and 16 byte arrays, for IPv4 and IPv6 respectively. I used to just use a []byte slice for this, and adjust accordingly based on len(x) when it mattered.
Actually, pretty much the only place it mattered was here:
func commonBits(ip1, ip2 []byte) uint8 {
if len(ip1) == 4 {
a := binary.BigEndian.Uint32(ip1)
b := binary.BigEndian.Uint32(ip2)
x := a ^ b
return uint8(bits.LeadingZeros32(x))
} else if len(ip1) == 16 {
a := binary.BigEndian.Uint64(ip1)
b := binary.BigEndian.Uint64(ip2)
x := a ^ b
if x != 0 {
return uint8(bits.LeadingZeros64(x))
}
a = binary.BigEndian.Uint64(ip1[8:])
b = binary.BigEndian.Uint64(ip2[8:])
x = a ^ b
return 64 + uint8(bits.LeadingZeros64(x))
} else {
panic("Wrong size bit string")
}
}So in converting this all away from slices and toward static array sizes, I made this new type constraint:
type ipArray interface {
[4]byte | [16]byte
}Then I broke out those two if clauses into their own functions:
func commonBits4(ip1, ip2 [4]byte) uint8 {
a := binary.BigEndian.Uint32(ip1[:])
b := binary.BigEndian.Uint32(ip2[:])
x := a ^ b
return uint8(bits.LeadingZeros32(x))
}
func commonBits16(ip1, ip2 [16]byte) uint8 {
a := binary.BigEndian.Uint64(ip1[:8])
b := binary.BigEndian.Uint64(ip2[:8])
x := a ^ b
if x != 0 {
return uint8(bits.LeadingZeros64(x))
}
a = binary.BigEndian.Uint64(ip1[8:])
b = binary.BigEndian.Uint64(ip2[8:])
x = a ^ b
return 64 + uint8(bits.LeadingZeros64(x))
}So far, so good, but what is the implementation of commonBits?
func commonBits[B ipArray](ip1, ip2 B) uint8 {
// ???
}If you try to convert the array to a slice, the compiler will bark at you. You can use any(ip1).(type) in a switch, but then you get runtime overhead. I've figured out a truly horrific trick that combines two Go 1.17 features into an unholy mess:
func giveMeA4[B ipArray](b B) [4]byte {
return *(*[4]byte)(unsafe.Slice(&b[0], 4))
}
func giveMeA16[B ipArray](b B) [16]byte {
return *(*[16]byte)(unsafe.Slice(&b[0], 16))
}
func commonBits[B ipArray](ip1, ip2 B) uint8 {
if len(ip1) == 4 {
return commonBits4(giveMeA4(ip1), giveMeA4(ip2))
} else if len(ip1) == 16 {
return commonBits16(giveMeA16(ip1), giveMeA16(ip2))
}
panic("Wrong size bit string")
}This... works, amazingly. Similarly, when I needed to adjust my randomized unit tests, I wound up going with code that looks like this:
var addr B
rand.Read(unsafe.Slice(&addr[0], len(addr)))I asked some Go experts if there was a better way, and the answer I got was that generics aren't yet well suited for arrays. So, I'm opening this rather vague report in hopes that it can turn into a proposal for something useful.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status