Go Constants Beyond the Basics
Untyped constants, exact compile-time arithmetic, iota bitmasks, and dead-code elimination in Go.
In Go, constants are not merely immutable variables. They are compile-time values evaluated with arbitrary-precision arithmetic, capable of remaining untyped until assigned or used in an expression.
Understanding how the compiler evaluates untyped constants unlocks cleaner APIs, compile-time assertions, and dead-code elimination.
Untyped Constants and Default Kinds
When declared without an explicit type, a constant in Go is untyped. Untyped constants have a default kind (int, rune, float64, complex128, string, or bool) that determines their type when assigned to a variable without explicit type annotations:
const x = 10 // untyped int (default type: int)
var i int = x
var f float64 = x // allowed: 10 represents a valid float64
var b byte = x // allowed: 10 fits in uint8graph TD
A["const x = 10\n(untyped integer)"] --> B["var i int = x"]
A --> C["var f float64 = x"]
A --> D["var b byte = x"]
B --> E["int (4 or 8 bytes)"]
C --> F["float64 (8 bytes)"]
D --> G["uint8 (1 byte)"]
class A diag-ingress;
class B,C,D diag-compute;
class E,F,G diag-storage;Untyped constants allow mixing numeric literals without explicit type conversions, provided the values are representable without precision loss:
const a = 1.5 // untyped float
const b = 2 // untyped int
const result = a * b // result is untyped float (3.0)High-Precision Compile-Time Arithmetic
The Go language specification (§Constants) mandates that numeric constants represent exact values of high precision:
- Integer constants must have at least 256 bits of precision.
- Floating-point constants must have at least 256 bits of mantissa and a 16-bit signed exponent.
The Go compiler (gc) evaluates constant expressions using multi-precision math at compile time. You can compute with numbers that far exceed the limits of int64 or float64:
const (
// Exact 512-bit intermediate representation at compile time
largeA = 1 << 100
largeB = largeA >> 90 // 1 << 10 = 1024
)
var count int = largeB // Valid: 1024 fits in standard intIf you attempt to assign largeA directly to an int64, compilation fails with constant 1267650600228229401496703205376 overflows int64.
Typed Constants and iota Bitmasks
The iota identifier represents successive untyped integer constants starting at 0 within a constant block. It is especially powerful for declaring bitwise flags and binary byte magnitudes:
type ByteSize uint64
const (
_ = iota // ignore first value (0)
KB ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20 = 1048576
GB // 1 << 30 = 1073741824
TB // 1 << 40 = 1099511627776
)graph LR
A["iota = 0\n(blank identifier _)"] --> B["Ignored (0)"]
C["iota = 1\n(KB)"] --> D["1 << 10 = 1,024 B"]
E["iota = 2\n(MB)"] --> F["1 << 20 = 1,048,576 B"]
G["iota = 3\n(GB)"] --> H["1 << 30 = 1,073,741,824 B"]
class A,B diag-slow;
class C,D diag-ingress;
class E,F diag-compute;
class G,H diag-storage;Compile-Time Dead-Code Elimination
Because constant expressions are evaluated during compilation, constant boolean guards in if statements allow zero-cost conditional compilation:
const EnableMetrics = false
func RecordMetric(name string, val float64) {
if !EnableMetrics {
return // Compiler eliminates all subsequent code in this block
}
pushToCollector(name, val)
}When EnableMetrics is false, the compiler’s SSA backend optimizes away the pushToCollector call entirely, reducing final binary size and removing branches without runtime overhead.
Constant Expression Built-Ins
Under the Go specification, only specific built-in functions can be evaluated in constant expressions:
lenandcap(when applied to strings, arrays, or array pointers)real,imag, andcomplex(for complex numbers)minandmax(introduced in Go 1.21, valid when all arguments are constants)unsafe.Sizeof,unsafe.Alignof, andunsafe.Offsetof
const bufferLen = 128
const maxCapacity = max(bufferLen, 256) // Compile-time constant (256)
const ptrSize = unsafe.Sizeof(uintptr(0)) // 8 on 64-bit platformsStandard library functions like math.Sqrt or math.Pow cannot be used in constant declarations because they execute runtime code.
Language Invariants
- Constants have no memory address: You cannot take the address of a constant (
&xis invalid). Constants are inlined as literals or registers directly in compiled machine instructions. - Untyped string literals are UTF-8: String constants represent immutable byte sequences guaranteed to be valid UTF-8 source code representations.
- No runtime overhead: Constant folding and arithmetic evaluation happen entirely during compiler passes, incurring zero CPU cycles at application runtime.