How to create constant uint32 tags with four ASCII codes at compile time?

Viewed 136

I need to create many uint32 tags which are mapping of ASCII letters. For instance the tag "abcd" is encoded as 0x61626364 where each byte correspond to the ASCII code of the letter.

A straightforward solution is to define the tag values like this

type Tag uint32

const Tag_abcd = Tag(0x61626364)

But this is error prone.

A less error prone solution would be to define the tag values with a function receiving the letters as argument.

const Tag_abcd = foo("abcd")

or like this as can easily been done with a macro in C

const Tag_abcd = bar('a','b','c','d')

But this would require support of functions evaluated at compile time. As far as I know, it is not possible with Go. Am I correct ? Could there be another way ?

3 Answers

You may assemble the constant using rune literals and bit shifts. It won't be too compact, but it will be "safe" (meaning you can see the characters in the constant expression):

const TagABCD Tag = 'a'<<24 + 'b'<<16 + 'c'<<8 + 'd'

Alternatively you may write it in multiple lines, so the letters are aligned in a column:

const TagABCD2 Tag = 0 +
    'a'<<24 +
    'b'<<16 +
    'c'<<8 +
    'd'

To expand on icza's answer, and to improve readability of the tag declaration, you can:

  • declare helper constants in the form <letter><number>, where <letter> is the related ASCII character and <number> is the position of that char in the 4-character tag.
  • bit-shift the rune by iota* 8
  • compose the tag constant by OR |'ing the helper constants
import "fmt"

const (
    a1 uint32 = 'a'<<(iota*8)
    a2
    a3
    a4
)

// other similar const declarations for b1,b2,b3,b4 and so on
// must repeat the keyword const to reset iota

const Tag_abcd = a4 | b3 | c2 | d1

const Tag_ddba = d4 | d3 | b2 | a1

func main() {
    fmt.Printf("%x\n", Tag_abcd) // 61626364
    fmt.Printf("%x\n", Tag_ddba) // 64646261
}

The advantage is that:

  • the tag declaration is probably easier to read and more straightforward for human maintainers
  • the helper identifiers can be easily refactored with IDE support

The disadvantage is that:

  • the source might become more verbose, but you can mitigate this by isolating the helper consts into a separate file
  • for uppercase ASCII, helper consts as A1 will become exported, so then you might have to prefix the identifier with _ or similar tricks

YMMV

Playground

You could use go generate to parse your compound constant value to different memory allocations.

it is quite complex to maintain this implementation though if you have a team that instantly needs to know what this does and when it is safe to refactor it

Related