4.4 Constants and the const Block
Right, let’s talk about constants. You’ve met variables, the flighty, changeable entities in your program. Constants are their more stubborn, reliable cousins. Once you declare a constant, its value is, well, constant. You cannot reassign it. This isn’t just about being pedantic; it’s a powerful tool for writing safer, more predictable, and more performant code. The compiler can make strong guarantees about constants, which allows it to perform optimizations and catch whole classes of bugs at compile time instead of letting them ruin your Friday evening.
You declare them with the const keyword, and you must initialize them immediately with a value the compiler can figure out at compile time.
const APIEndpoint = "https://api.yourawesomeservice.com/v1"
const MaxRetries = 3
const Pi = 3.14159265358979323846
Try to change MaxRetries later in your code (MaxRetries = 5), and the compiler will stop you with a cheerful: “cannot assign to MaxRetries”. It’s not being mean; it’s saving you from yourself.
The Glorious const Block
Writing a bunch of individual const declarations is a bit like buying cutlery one piece at a time. Enter the const block. It lets you group related constants together, which is not only more organized but also provides a nifty feature called implicit repetition. The syntax is clean and avoids repeating const ad nauseam.
const (
StatusOK = 200
StatusCreated = 201
StatusAccepted = 202
StatusNotFound = 404
StatusInternalServerError = 500
)
But here’s where it gets clever. Go allows you to be lazy in the best way possible. If you omit the value in a constant declaration inside a block, it automatically repeats the value and type from the previous declaration. This is perfect for creating sequences of values, most famously, iota.
Enter iota, the Elegant Counter
Ah, iota. The name comes from a Greek letter, but in Go, it’s a brilliantly succinct way to define a sequence of related constants. It’s a pre-declared identifier that represents an integer constant that starts at zero and increments by one for each item in the const block. It resets to 0 in every new const block.
Let’s say you’re defining the states for a simple process. You could do this:
const (
StatePending = 0
StateRunning = 1
StateCompleted = 2
StateFailed = 3
)
But why manually assign and update numbers? That’s a future typo waiting to happen. Use iota:
const (
StatePending = iota // 0
StateRunning // 1
StateCompleted // 2
StateFailed // 3
)
The compiler handles the incrementing. It’s self-documenting and maintainable. If you need to insert a new state, say StateQueued after StatePending, you just add a line. The numbers auto-adjust. It’s magic, but the good kind—the kind that actually works.
You can use it for more than just successive integers. You can use expressions with iota. A classic example is defining bitmask flags:
const (
ReadPermission = 1 << iota // 1 << 0 which is 1 (binary: 0001)
WritePermission // 1 << 1 which is 2 (binary: 0010)
ExecutePermission // 1 << 2 which is 4 (binary: 0100)
)
Now you can combine these permissions (user.Permission = ReadPermission | WritePermission) in a clean, standardized way. This is how a lot of the standard library itself operates.
The Rules (Because There Are Always Rules)
No Runtime Allowed: The value you assign to a constant must be determinable at compile time. This means you cannot assign a value returned by a function call (like
math.Sqrt(2)ortime.Now()), the value of a variable, or anything else that can only be resolved when the program is actually running. This is the fundamental difference betweenconstandvar. If you need a value set at runtime that shouldn’t change, you use a variable and just… don’t change it (or maybe look intohttp://golang.org/pkg/reflect/#Value.CanSetif you’re feeling fancy).Typed vs. Untyped Constants: This is a subtle but super important concept. When you write
const Greeting = "Hello", you’re creating an untyped constant. It has a kind (string) but not a definitive type. It remains flexible until it’s used in a context that requires a specific type. This is why you can do things like:const delta = 5 // untyped integer constant var f float64 = delta // OK! delta behaves like a float64 here. var i int = delta // Also OK! delta behaves like an int here.If you declare a typed constant (
const delta int = 5), that flexibility is gone.var f float64 = deltawould cause a compile-time error because you’re trying to assign anintto afloat64. Generally, omitting the explicit type is the more flexible and idiomatic choice unless you have a specific reason to nail it down.
So, use constants liberally. Use them for magic numbers, configuration values, and any concept in your code that is fundamentally fixed. It makes your intent clear to the compiler, to your tools, and most importantly, to the next developer (which is probably future-you). And for heaven’s sake, use iota instead of manually writing out 0, 1, 2, 3. You’re not a human counter; you’re a programmer. Let the language work for you.