Go007 Golang Constants and Variables

Golang Constants and Variables

In this article we mentioned all unnamed constants (or literal constants), except false
and true, which are two predeclared (built-in) named constants. Custom named
constant declarations will be introduced below in this article.

Untyped values and Typed values

Untyped value means the type of the value has not been confirmed yet.
On the contrary, typed values are type determinated values. Most untyped
values got it’s default type, but the predeclared one nil is the only
untyped value which got no default value.

All literal constants(unnamed constants) are untyped values. Most untyped
values in go are literal constants and unnamed constants. The
other untyped values include nil and some boolean results returned by
some operations which will be introduced in other articles later.

The default type of a literal constant is determined by its literal form:

  • The default type of a string literal is string
  • The default type of a boolean literal is bool
  • The default type of an integer literal is int
  • The default type of a rune literal is rune (a.k.a., int32)
  • The default type of a floating-point literal is float64
  • If a literal contains an imaginary part, then its default type is complex128

Explicit Conversion (Untyped Constants)

Go supports value conversions(which other languages also do). We can
use the form T(v) to convert a value v to the type denoted by T
(or simply speaking, type T). If the conversion T(v) is legal, Go
compilers view T(v) as a typed value of type T. But, for a certain
type T, to make the conversion T(v) legal, the value v can’t be
arbitrary.

The following mentioned rules apply for both the literal constants
and the untyped named constants(which will be introduced soon).

For an untyped constant value v, there are two scenarios where T(v)
is legal.

  • v (literal value) is representable as a value of a basic type T,
    The result value(T(v)) is a typed constant of type T.
  • The default type of v is an integer type (int or rune) and T is a
    string type. The result of T(v) is a string of type T and contains
    the UTF-8 representation of the integer as a Unicode code point,
    Integer values outside the range of valid Unicode code points result
    strings represented by \uFFFD (a.k.a., “\xef\xbf\xbd”). 0xFFFD
    is the code point for the Unicode replacement character. Note: The
    result string of a conversion from an integer always contains one
    and only one rune.

In fact, the second scenario doesn’t require v to be a constant. If v
is a constant, then the result of the conversion is also a constant,
otherwise, the result is not a constant.

There are some legal examples:

 1// Rounding happens here
 2complex128(1+-1e-1000i) // (1 - 0i)
 3float32(0.49999999) // (0.5)
 4float32(17000000000000000) // rounded to more precise representation of fload32
 5
 6// No rounding in the these lines.
 7float32(123) // 123
 8uint(1.0) // 1
 9int8(-123) // -123
10int16(6+0i) // 6
11complex128(789) // 789 +0i
12
13string(65)          // "A"
14string('A')         // "A"
15string('\u68ee')    // "森"
16string(-1)          // "\uFFFD"
17string(0xFFFD)      // "\uFFFD"
18string(0x2FFFFFFFF) // "\uFFFD"

There are some illegal examples:

 1// 1.23 is not representable as a value of int.
 2int(1.23)
 3// -1 is not representable as a value of uint8.
 4uint8(-1)
 5// 1+2i is not representable as a value of float64.
 6float64(1+2i)
 7
 8// Constant -1e+1000 overflows float64.
 9float64(-1e1000)
10// Constant 0x10000000000000000 overflows int.
11int(0x10000000000000000)
12
13// The default type of 65.0 is float64,
14// which is not an integer type.
15string(65.0)
16// The default type of 66+0i is complex128,
17// which is not an integer type.
18string(66+0i)

Note: sometimes, the form of explicit conversions must be written as
(T)(v) to avoid ambiguities. Such situations often happen in case
of T is not an identifier. Thus, the type starts with the operator
* or <-, or if the type starts with the keyword func and has no
result list, it must be parenthesized when necessary to avoid ambiguity:

1*Point(p)        // same as *(Point(p))
2(*Point)(p)      // p is converted to *Point
3<-chan int(c)    // same as <-(chan int(c))
4(<-chan int)(c)  // c is converted to <-chan int
5func()(x)        // function signature func() x
6(func())(x)      // x is converted to func()
7(func() int)(x)  // x is converted to func() int
8func() int(x)    // x is converted to func() int (unambiguous)

Type Inference

Type inference(a.k.a., type deduction) are supported in golang. In many
circumstances, programmers don’t need to explicitly specify the types of
some values in code. Go compilers will deduce the types for these values
by context.

In Go code, if a place needs a value of a certain type and an untyped
value (often a constant) is representable as a value of the certain type,
then the untyped value can be used in the place. Go compilers will view
the untyped value as a typed value of the certain type. Such places
include an operand in an operator operation, an argument in a function
call, a destination value or a source value in an assignment, etc.

Some circumstances have no requirements on the types of the used
values. If an untyped value is used in such a circumstance, Go
compilers will treat the untyped value as a typed value of its default
type.

The two type deduction cases can be viewed as implicit conversions.

Constants Declaration

There are boolean constants, rune constants, integer constants,
floating-point constants, complex constants, and string constants
.
Rune, integer, floating-point, and complex constants are collectively
called numeric constants. The keyword const is used to declare
named constants.

Note: constants may be typed or untyped. Literal constants, true,
false, iota, and certain constant expressions containing only
untyped constant operands are untyped.

A constant may be given a type explicitly by a constant declaration or
conversion, or implicitly when used in a variable declaration or an
assignment or as an operand in an expression. It is an error if the
constant value cannot be represented as a value of the respective type.

An untyped constant has a default type which is the type to which the
constant is implicitly converted in contexts where a typed value is
required, for instance, in a short variable declaration such as i := 0
where there is no explicit type. The default type of an untyped constant
is bool, rune, int, float64, complex128 or string
respectively, depending on whether it is a boolean, rune, integer,
floating-point, complex, or string constant.

There are some untyped constants(named) declaration, include type deductions:

 1const Pi = 3.14159265358979323846 //
 2const zero = 0.0         // type deductions: untyped floating-point constant
 3const (
 4	size = 1024
 5	eof  = -1  // type deduction: untyped integer constant
 6  // order are not considered to be necessary
 7  bad  = !good
 8  good = true
 9)
10
11// type deduction
12const a, b, c = 3, 4, "foo"  // a = 3, b = 4, c = "foo", untyped integer and string constants
13const u, v = 0, 3    // u = 0.0, v = 3.0

In specification, each of the lines containing a = symbol in the
above constant declaration group as a constant specification.

Note1: Constants can be declared both at package level (out of any
function body) and in function bodies. The constants declared in
function bodies are called local constants. The variables declared
out of any function body are called package-level constants. We
also often call package-level constants as global constants.

Note2: = in constants declaration means bound to not asign to,
each constant specifacation means a declared identifier(the name of
the constant) is bound to the literal value offerd on the right side of =.

Typed named constants

Typed constants are all named. As fllows, all the four declared constants
are typed values. The types of X and Y are both float32 and the types of
A and B are both int64.

1const X float32 = 3.1416
2
3const (
4	A, B int64   = -3, 5
5	Y    float32 = 2.718
6)

If multiple typed constants are declared in the same constant specification,
then their types must be the same, just as the constants A and B in the
above example.

We can also use explicit conversions to provide enough information for
Go compilers to deduce the types of typed named constants. The above
code snippet is equivalent to the following one, in which X, Y, A and B
are all typed constants.

1const X = float32(3.14)
2
3const (
4	A, B = int64(-3), int64(5)
5	Y    = float32(2.718)
6)

If a basic value literal is bound to a typed constant, the basic value
literal must be representable as a value of the type of the constant.
The following typed constant declarations are invalid.(^is bitwise-not)
operator.

 1// error: 256 overflows uint8
 2const a uint8 = 256
 3// error: 256 overflows uint8
 4const b = uint8(255) + uint8(1)
 5// error: 128 overflows int8
 6const c = int8(-128) / int8(-1)
 7// error: -1 overflows uint
 8const MaxUint_a = uint(^0)
 9// error: -1 overflows uint
10const MaxUint_b uint = ^0

The following typed constant declaration is valid on 64-bit OSes, but
invalid on 32-bit OSes. For each uint value has only 32 bits on 32-bit
OSes. (1 « 64) - 1 is not representable as 32-bit values.(<< is
bitwise-left-shift operator.)

1const MaxUint uint = (1 << 64) - 1

Then how to declare a typed uint constant and bind the largest uint value
to it? Use the following way instead.

1// for a uint literal value 'x', ^x = MaxUint - x
2// here: ^0 = math.MaxUint - 0 = math.MaxUint
3const MaxUint = ^uint(0)

Similarly, we can declare a typed int constant and bind the largest int
value to it. (>> is bitwise-right-shift operator.)

1// math.MaxInt * 2 + 1 == math.MaxUint
2const MaxInt = int(^uint(0) >> 1)

A similar method can be used to get the number of bits of a native word,
and check the current OS is 32-bit or 64-bit.

1// NativeWordBits is 64 or 32.
2const NativeWordBits = 32 << (^uint(0) >> 63)
3const Is64bitOS = ^uint(0) >> 63 != 0
4const Is32bitOS = ^uint(0) >> 32 == 0

Autocomplete in constant declarations

In a group-style constant declaration, except the first constant
specification, other constant specifications can be incomplete. An
incomplete constant specification doesn’t contain the = symbol.
Compilers will autocomplete the incomplete lines for us by copying the
missing part from the first preceding complete constant specification.
For example, at compile time, compilers will automatically complete the
following code:

 1const (
 2	X float32 = 3.14
 3	Y           // here must be one identifier
 4	Z           // here must be one identifier
 5
 6	A, B = "Go", "language"
 7	C, _
 8	// In the above line, the blank identifier
 9  // is required to be present.
10)

codes are equivalent above and below:

1const (
2  X float32 = 3.14
3  Y float32 = 3.14
4  Z float32 = 3.14
5
6  A, B = "Go", "language"
7  C, _ = "Go", "language"
8)

iota in constant declaration

With in a constant declaration, the predeclared identifier iota is
used to represents successive untyped integer constants. It’s value
is the index of the respective constant specification in that constant
declaration, starting at zero
. It’s used to construct a set of related
constants:

 1const (
 2  a = iota // a = 0(int)
 3  b = iota // b = 1(int)
 4  c = iota // c = 2(int)
 5)
 6// or
 7const (
 8  a = iota // a = 0(int)
 9  b
10  c
11)
12
13// iota usually appear in a group-style constants declaration
14// and can appear at any index
15const (
16	a    int              = 32 // iota == 0, a == 32
17	b                          // iota == 1, b == 32
18	_                          // iota == 2
19	c    = iota                // iota == 3, c == 3
20	d, e = iota, iota - 1      // d, e = 4, 4-1
21	_, f                       // _, f = 5, 5-1
22	g, _                       // g, _ = 6, 6-1
23	h    = 22.2                // iota == 7, h float64 = 22.200000
24	i    = iota                // iota == 8, i == 8
25)

Variables, Variable Declarations and Value Assignments

All Variables are named typed values, they are stored in memory at
runtime and can be modified. When declaring a variable, there must be
sufficient information provided for compilers to deduce the type of
the variable.

  • Local Variables: variables declared within function bodies are
    called local variables.
  • Global Variables: variables declared out of any function body are
    called package-level variables, a.k.a. global variables.

There are two basic variable declaration forms, the standard one and
the short one. Note: The short form can only be used to declare local
variables.

Standard variable declaration forms

full standard variable declaration form

1// one variable a line
2var hi string = "hello"
3var pi float64 = 3.1415926
4// multi variables a line
5var name, location string = "suo.li", "Beijing"
6var goodkid, goodmood bool = true, false

omit type when declaration
There are some standard declarations without specify type, and the
compiler will deduce the type based on the initial value offered:

1// one variable a line
2var hi = "hello" // deduced as string
3var pi = 3.1415926 // deduced as float64
4// multi variables a line
5var name, location = "suo.li", "Beijing"
6var goodkid, goodmood = true, false
7// alse different types can live on the same line
8// got, name will be deduced as boolean, string respectively
9var got, name = true, "suo.li"

Note: different types can be specified in a same declaration, when
omit type. And the type deduction can be viewd as implicit conversions.

omit initial value when declaration
There are some standard declarations without specify initial value,
and they will be initialized with the zero value of that type:

1// both initialized as blank string ""
2var site, lang string
3// both initialized as boolean false
4var got, good bool
5// booth initialized as int 0
6var inc int

Note: multi variable declarations can be grouped with (), as follows:

1var (
2	a           int
3	b           float64 = 3.1415926
4	name        string
5	got         bool = true
6	site, visit      = "suosuoli.com", 20222
7)

Pure value assignment

When declare a variable, = means assignment. After a variable was
declared, we can modify it’s value by using pure value assignments.
left of = we called destination(or target) of assignment, the
value on the right side called the source of assignment:

 1var i1 int
 2// here: a is the destination of pure assignment, 34 is the source
 3i1 = 34
 4const pi = 3.14
 5const n = 23
 6var f1, f2 float64
 7
 8pi = 3.1415 // error: constant is immutable
 9f1 = pi // ok: pi is deduced as float64
10n = f1 // error: n is a constant
11i1 = f2 // error: type mismatch
12i1 = n // ok: n is deduced at int
13f1 = f2 // ok
14_ = i1 // ok
15
16i1, f1 = f1, i1 // error: type mismatch
17i1, f1 = int(f1), float32(i1) // ok
18f1, f2 = f2, f1 // ok
19_, f1 = f2, f1 // ok
20f1, _ = f2, f1 // ok
21_, _ = f2, f1 // ok
22f1, f2 = 1, 1.1 // ok

Note:

  1. Target of pure assignment must addressable values, map index
    expression or the blank identifire _.
  2. Constants are immutable values, they can’t be placed at left side
    of =, they only can show up at right side of =. Variables can
    show up at both side of =.
  3. The blank identifier _ can only show up at left side of =, it
    means we ignore the target value of the pure assignment.

Short variable declaration forms

Short variable declaration forms are also usually used when coding,
but short variable declaration forms may only be used in a function
to declare local variables, they also can be appear in some context
such as the initializer for if, for, switch statements, to
declare local temporary variables.

There are some short forms for declaring variables:

 1// string
 2name := "suo.li"
 3// int
 4age := 26
 5// boolean, string
 6got, good := true, "yes"
 7// int, float64, int
 8a, b, c := 1, 2.3, 45
 9
10f := func() int { return 7 }
11ch := make(chan int)
12r, w, _ := os.Pipe()  // os.Pipe() returns a connected pair of Files and an error
13_, y, _ := coord(p)   // coord() returns three values; only interested in y coordinate

When there are multi variables lay on left of :=, these variables
can be redclared, but at least one of the non-blank variables left
of := is new, then the compiler will happy. Redeclaration can only
appear in a multi-variable short declaration. Redeclaration does not
introduce a new variable; it just assigns a new value to the original.

 1name, age, location := func() (string, int, string) {
 2		return "suo.li", 22, "beijing"
 3}()
 4
 5// ok, happy is a new variable
 6// and value of vaiable of name and age is changed
 7name, age, happy := "", 25, true
 8
 9// error: no new variables on left side of :=
10name, age, location := "", 44, "gz"
11
12// error: no new variables on left side of :=
13name, age, _ := "", 25, true

So, differences between short and standard variable declarations:

  1. In the short declaration form, the var keyword and variable
    types must be omitted.
  2. The assignment sign must be := instead of =.
  3. In the short variable declaration, old variables and new
    variables can mix at the left of :=. But there must be at
    least one new variable at the left.

Note:

  1. Each local declared variable must be used at least once effectively
  2. Package-level variables are initialized in specific order when
    they have dependency relations
     1package main
     2
     3// initialization order:
     4// y = 5, c = y, b = c+1, a = b+1, and x = a+1.
     5var x, y = a+1, 5         // 8 5
     6var a, b, c = b+1, c+1, y // 7 6 5
     7
     8func main() {
     9// compiler error: undeclared mam: a,b,c
    10	var x, y = a + 1, 5           // 8 5
    11	var a, b, c = b + 1, c + 1, y // 7 6 5
    12}
    

Scopes of Variables and Named Constants

Code blocks in go are formed with {...}, code block can be nested.
A variable or a named constant declared in an inner code block will
shadow(inner block code will get a value of inner block variabl or
named constant) the variables and constants declared with the same
name in outer code blocks.

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7// warn: a, b is not used
 8const b = 12
 9var a int = 32
10
11func main() {
12	printAny([]interface{}{})
13	var a int = 20
14	var b int = 21
15
16	{
17
18		// a,b are new declared variable
19		a, b := b, a
20		fmt.Println(a, b) // 21,20
21
22		// on the right sied, a,b shadow the outer a,b
23		a, c := a+1, b+2
24		fmt.Println(a, b, c) // 22,20,22
25	}
26
27	fmt.Println(a, b) // 20,21
28	fmt.Pritln(c)     // c is not declared
29}

The value denoted by an untyped constant can overflow its default type

For example, the following code compiles okay.

 1// 3 untyped named constants. Their bound
 2// values all overflow their respective
 3// default types. This is allowed.
 4const n = 1 << 64          // overflows int
 5const r = 'a' + 0x7FFFFFFF // overflows rune
 6const x = 2e+308           // overflows float64
 7
 8func main() {
 9	_ = n >> 2
10	_ = r - 0x7FFFFFFF
11	_ = x / 2
12}

But the following code does’t compile, for the constants are all typed.

1// 3 typed named constants. Their bound
2// values are not allowed to overflow their
3// respective default types. The 3 lines
4// all fail to compile.
5const n int = 1 << 64           // overflows int
6const r rune = 'a' + 0x7FFFFFFF // overflows rune
7const x float64 = 2e+308        // overflows float64

Each named constant identifier will be replaced with its bound literal value at compile time

Constant declarations can be viewed as enhanced #define macros in C. A
constant declaration defines a named constant which represents a
literal. All the occurrences of a named constant will be replaced with
the literal it represents at compile time.

If the two operands of an operator operation are both constants, then
the operation will be evaluated at compile time. Please read the next
article common operators for details.

For example, at compile time, the following code

 1package main
 2
 3const X = 3
 4const Y = X + X
 5var a = X
 6
 7func main() {
 8	b := Y
 9	println(a, b, X, Y)
10}
11will be viewed as
12package main
13
14var a = 3
15
16func main() {
17	b := 6
18	println(a, b, 3, 6)
19}

Go006 Golang Builtin Types and Value Literals