— notes
Go Constants Beyond the Basics
On this page
When I first got into Go, I thought constants were simple and limited – just fixed values, nothing fancy. But as I delved deeper, I found they’re quite versatile. Yes, they’re fixed values, but Go handles them in ways that are both flexible and efficient. Let’s see what that means with some practical examples.
Constants as Type-Free Values (Until They’re Used)
In Go, constants are often untyped until you actually use them. They have a default kind but can be assigned to variables of different types, as long as the value fits. This makes them adaptable in a way that’s unusual for a statically typed language.
Here’s how that looks:
const x = 10
var i int = x
var f float64 = x
var b byte = xgraph TD
A["const x = 5\n(untyped)"] --> B["used as int"]
A --> C["used as float64"]
A --> D["used as byte"]
B --> E["int"]
C --> F["float64"]
D --> G["byte"]A bit analogous to Schrodinger’s paradox, x can be an int, a float64, or even a byte until you assign it. This temporary flexibility lets x work smoothly with different types in your code. No need for casting, which keeps things neat.
You can even mix constants of different types in expressions and Go will figure out the best type for the result:
const a = 1.5
const b = 2
const result = a * b // result is float64Since a is a floating-point number, Go promotes the whole expression to float64. So you don’t have to worry about losing precision – Go handles it. But be careful: if you try to assign result to an int, you’ll get an error. Go doesn’t allow implicit conversions that might lose data.
Constants in Conditional Compilation
Go doesn’t have a preprocessor like some other languages, but you can use constants in if statements to include or exclude code at compile time.
const debug = false
func main() {
if debug {
fmt.Println("Debugging enabled")
}
}When debug is false, the compiler knows the if condition will never be true and might leave out the code inside the block. This can make your final binary smaller.
Working with Big Numbers
One powerful feature of Go’s constants is that they support very large numbers. Untyped numeric constants in Go have “infinite” precision, limited only by memory and the compiler.
const bigNum = 1e1000 // This is a valid constantEven though bigNum is way bigger than any built-in numeric type like float64 or int, Go lets you define it as a constant. You can do calculations with these large numbers at compile time:
const (
a = 1e20
b = 1e30
c = a * b // c is 1e50
)Typed Constants with iota
If you’ve been using Go, you’ve probably seen iota for creating enumerated constants.
const (
_ = iota
KB = 1 << (10 * iota)
MB
GB
TB
)graph LR
A["iota=0"] --> B["1<<0 = 1\n(Read)"]
C["iota=1"] --> D["1<<1 = 2\n(Write)"]
E["iota=2"] --> F["1<<2 = 4\n(Execute)"]
B --> G["Read | Write = 3"]
D --> GLimitations with Constants
While Go’s constants are flexible, there are some things they can’t do.
1. Constants Cannot Be Referenced by Pointers
Constants don’t have a memory address at runtime. So you can’t take the address of a constant or use a pointer to it.
const x = 10
var p = &x // Error: cannot take the address of x2. Function Calls in Constant Declarations
Only certain built-in functions can be used in constant expressions, like len, cap, real, imag, and complex.
const str = "hello"
const length = len(str) // This works
const pow = math.Pow(2, 3) // Error: math.Pow cannot be used in constant expressions