Constants
In Golang, we use the term constant
to represent fixed (unchanging) values such as 5
, 1.34
, true
, "Hello"
etc.
Literals are constants
All the literals in Golang, be it integer literals like 5
, 1000
, or floating-point literals like 4.76
, 1.89
, or boolean literals like true
, false
, or string literals like "Hello"
, "John"
are constants.
Constants | Examples |
---|---|
integer constants | 1000 , 67413 |
floating-point constants | 4.56 , 128.372 |
boolean constants | true , false |
rune constants | 'C' , 'ä' |
complex constants | 2.7i , 3 + 5i |
string constants | "Hello" , "Rajeev" |
Declaring a Constant
Literals are constants without a name. To declare a constant and give it a name, you can use the const
keyword like so -
const myFavLanguage = "Python"
const sunRisesInTheEast = true
You can also specify a type in the declaration like this -
const a int = 1234
const b string = "Hi"
Multiple declarations in a single statement is also possible -
const country, code = "India", 91
const (
employeeId string = "E101"
salary float64 = 50000.0
)
Constants, as you would expect, cannot be changed. That is, you cannot re-assign a constant to a different value after it is initialized -
const a = 123
a = 321 // Compiler Error (Cannot assign to constant)
Typed and Untyped Constants
Constants in golang are special. They work differently from how they work in other languages. To understand why they are special and how they exactly work, we need some background on Go’s type system. So let’s jump right into it -
Background
Go is a statically typed programming language. Which means that the type of every variable is known or inferred by the compiler at compile time.
But it goes a step further with its type system and doesn’t even allow you to perform operations that mix numeric types. For example, You cannot add a float64
variable to an int
, or even an int64
variable to an int
-
var myFloat float64 = 21.54
var myInt int = 562
var myInt64 int64 = 120
var res1 = myFloat + myInt // Not Allowed (Compiler Error)
var res2 = myInt + myInt64 // Not Allowed (Compiler Error)
For the above operations to work, you’ll need to explicitly cast the variables so that all of them are of the same type -
var res1 = myFloat + float64(myInt) // Works
var res2 = myInt + int(myInt64) // Works
If you’ve worked with other statically typed languages like C, C++ or Java, then you must be aware that they automatically convert smaller types to larger types whenever you mix them in any operation. For example, int
can be automatically converted to long
, float
or double
.
So the obvious question is that - why doesn’t Go do the same? why doesn’t it perform implicit type conversions like C, C++ or Java?
And here is what Go designers have to say about this (Quoting from Golang’s official doc) -
The convenience of automatic conversion between numeric types in C is outweighed by the confusion it causes. When is an expression unsigned? How big is the value? Does it overflow? Is the result portable, independent of the machine on which it executes? It also complicates the compiler; “the usual arithmetic conversions” are not easy to implement and inconsistent across architectures. For reasons of portability, we decided to make things clear and straightforward at the cost of some explicit conversions in the code. (Excerpt from Golang’s official doc)
All right! So Go doesn’t provide implicit type conversions and it requires us to do explicit type casting whenever we mix variables of multiple types in an operation.
But how does Go’s type system work with constants? Given that all of the following statements are valid in Golang -
var myInt32 int32 = 10
var myInt int = 10
var myFloat64 float64 = 10
var myComplex complex64 = 10
What is the type of the constant value 10
in the above examples? Moreover, if there are no implicit type conversions in Golang, then wouldn’t we need to write the above statements like -
var myInt32 int32 = int32(10)
var myFloat64 float64 = float64(10)
// etc..
Well, the answers to all theses questions lay in the way constants are handled in Golang. So let’s find out how they are handled.
Untyped Constants
Any constant in golang, named or unnamed, is untyped unless given a type explicitly. For example, all of the following constants are untyped -
1 // untyped integer constant
4.5 // untyped floating-point constant
true // untyped boolean constant
"Hello" // untyped string constant
They are untyped even after you give them a name -
const a = 1
const f = 4.5
const b = true
const s = "Hello"
Now, you might be wondering that I’m using terms like integer
constant, string
constant, and I’m also saying that they are untyped.
Well yes, the value 1
is an integer, 4.5
is a float, and "Hello"
is a string. But they are just values. They are not given a fixed type yet, like int32
or float64
or string
, that would force them to obey Go’s strict type rules.
The fact that the value 1
is untyped allows us to assign it to any variable whose type is compatible with integers -
var myInt int = 1
var myFloat float64 = 1
var myComplex complex64 = 1
Note that, Although the value 1
is untyped, it is an untyped integer. So it can only be used where an integer is allowed. You cannot assign it to a string
or a boolean
variable for example.
Similarly, an untyped floating-point constant like 4.5
can be used anywhere a floating-point value is allowed -
var myFloat32 float32 = 4.5
var myComplex64 complex64 = 4.5
Let’s now see an example of an untyped string constant-
In Golang, you can create a type alias using the type
keyword like so-
type RichString string // Type alias of `string`
Given the strongly typed nature of Golang, you can’t assign a string
variable to a RichString
variable-
var myString string = "Hello"
var myRichString RichString = myString // Won't work.
But, you can assign an untyped string constant to a RichString
variable because it is compatible with strings -
const myUntypedString = "Hello"
var myRichString RichString = myUntypedString // Works
Constants and Type inference: Default Type
Go supports type inference. That is, it can infer the type of a variable from the value that is used to initialize it. So you can declare a variable with an initial value, but without any type information, and Go will automatically determine the type -
var a = 5 // Go compiler automatically infers the type of the variable `a`
But how does it work? Given that constants in Golang are untyped, what will be the type of the variable a
in the above example? Will it be int8
or int16
or int32
or int64
or int
?
Well, it turns out that every untyped constant in Golang has a default type. The default type is used when we assign the constant to a variable that doesn’t have any explicit type available.
Following are the default types for various constants in Golang -
Constants | Default Type |
---|---|
integers (10, 76) | int |
floats (3.14, 7.92) | float64 |
complex numbers (3+5i) | complex128 |
characters ('a' , '♠' ) | rune |
booleans (true, false) | bool |
strings (“Hello”) | string |
So, in the statement var a = 5
, since no explicit type information is available, the default type for integer constants is used to determine the type of a
, which is int
.
Typed Constants
In Golang, Constants are typed when you explicitly specify the type in the declaration like this-
const typedInt int = 1 // Typed constant
Just like variables, all the rules of Go’s type system applies to typed constant. For example, you cannot assign a typed integer constant to a float variable -
var myFloat64 float64 = typedInt // Compiler Error
With typed constants, you lose all the flexibility that comes with untyped constants like assigning them to any variable of compatible type or mixing them in mathematical operations. So you should declare a type for a constant only if it’s absolutely necessary. Otherwise, just declare constants without a type.
Constant expressions
The fact that constants are untyped (unless given a type explicitly) allows you to mix them in any expression freely.
So you can have a contant expression containing a mix of various untyped constants as long as those untyped constants are compatible with each other -
const a = 5 + 7.5 // Valid
const b = 12/5 // Valid
const c = 'z' + 1 // Valid
const d = "Hey" + true // Invalid (untyped string constant and untyped boolean constant are not compatible with each other)
The evaluation of constant expressions and their result follows certain rules. Let’s look at those rules -
Rules for constant expressions
A comparison operation between two untyped constants always outputs an untyped boolean constant.
const a = 7.5 > 5 // true (untyped boolean constant) const b = "xyz" < "uvw" // false (untyped boolean constant)
For any other operation (except shift) -
If both the operands are of the same type (ex - both are untyped integer constants), the result is also of the same type. For example, the expression
25/2
yields12
not12.5
. Since both the operands are untyped integers, the result is truncated to an integer.If the operands are of different type, the result is of the operand’s type that is broader as per the rule:
integer < rune < floating-point < complex
.const a = 25/2 // 12 (untyped integer constant) const b = (6+8i)/2 // (3+4i) (untyped complex constant)
Shift operation rules are a bit complex. First of all, there are some requirements -
The right operand of a shift expression must either have an unsigned integer type or be an untyped constant that can represent a value of type
uint
.The left operand must either have an integer type or be an untyped constant that can represent a value of type
int
.The rule - If the left operand of a shift expression is an untyped constant, the result is an untyped integer constant; otherwise the result is of the same type as the left operand.
const a = 1 << 5 // 32 (untyped integer constant) const b = int32(1) << 4 // 16 (int32) const c = 16.0 >> 2 // 4 (untyped integer constant) - 16.0 can represent a value of type `int` const d = 32 >> 3.0 // 4 (untyped integer constant) - 3.0 can represent a value of type `uint` const e = 10.50 << 2 // ILLEGAL (10.50 can't represent a value of type `int`) const f = 64 >> -2 // ILLEGAL (The right operand must be an unsigned int or an untyped constant compatible with `uint`)
Constant Expression Examples
Let’s see some examples of constant expressions -
package main
import "fmt"
func main() {
var result = 25/2
fmt.Printf("result is %v which is of type %T\n", result, result)
}
# Output
result is 12 which is of type int
Since both 25
and 2
are untyped integer constants, the result is truncated to an untyped integer 12
.
To get the correct result, you can do one of the following:
// Use a float value in numerator or denominator
var result = 25.0/2
// Explicitly cast the numerator or the denominator
var result = float64(25)/2
Let’s see another example -
package main
import "fmt"
func main() {
var result = 4.5 + (10 - 5) * (3 + 2)/2
fmt.Println(result)
}
What will be the result of the above program?
Well, it’s not 17
. The actual result of the above program is 16.5
. Let’s go through the evaluation order of the expression to understand why the result is 16.5
4.5 + (10 - 5) * (3 + 2)/2
↓
4.5 + (5) * (3 + 2)/2
↓
4.5 + (5) * (5)/2
↓
4.5 + (25)/2
↓
4.5 + 12
↓
16.5
You got it right? The result is wrong because the expression 25/2
is evaluated to 12
.
To get the correct result, you can do one of the following:
// Use a float value in the numerator or denominator
var result = 4.5 + (10 - 5) * (3 + 2)/2.0
// Explicitly cast numerator or the denominator
var result = 4.5 + float64((10 - 5) * (3 + 2))/2
Conclusion
Untyped constants are an amazing design decision taken by Go creators. Although Go has a strong type system, You can use untyped constants to escape from Go’s type system and have more flexibility when working with mixed data types in any expression.
As always, Thanks for reading. Please share your feedback about this article, or ask any question that you might have in the comment section below.
Next Article: Golang Control Flow Statements: If, Switch and For
Code Samples: github.com/callicoder/golang-tutorials