Go

How to Write Go Code

Basic constructs and elementary data types

Filenames, Keywords and Identifiers

Packages, import and visibility

hello_world.go

Every go file belongs to one and only one package (like a library or namespace in other languages). Many different .go files can belong to one package, so the filename(s) and package name are generally not the same.

An application can consist of different packages. Even if you use only package main, you don’t have to stuff all code in one big file: you can make a number of smaller files each having package main as the first codeline. If you compile a source file with a package name other than main, like pack1, the object file is stored in pack1.a.

The standard library contains ready-to-use packages of the Go installation.

Package compilation
Import

A Go program is created by linking together a set of packages through the import keyword. For example, import "fmt" tells Go that this program needs (functions, or other elements, from) the package fmt.

If multiple packages are needed, they can each be imported by a separate statement:

import "fmt"
import "os"

The shorter and more elegant way (called factoring the keyword, also applicable to const, var and type) is available (it is also clearer to list the package names in alphabetical order):

import (
    "fmt"
    "os"
)

Apart from _, identifiers of code-objects have to be unique in a package: there can be no naming conflicts. But the same identifier can be used in different packages: the package name qualifies it to be different.

Visibility rule

Packages expose their code-objects to code outside of the package according to the following rule:

Some notes on this rule:

A package can also be given another name (an alias), for example:

package main

import fm "fmt"  // alias

func main() {
    fm.Println("hello, world")
}

Importing a package which is not used in the rest of the code is a build-error.

Package level declarations and initializations

After the import statement, zero or more constants (const), variables (var), and types (type) can be declared; these are global (have package scope) and are known in all functions in the code (like c and v in gotemplate.go below), and they are followed by one or more functions (func).

Functions

The simplest function declaration has the format:

func functionName()

A main function as starting is required. The main function must have no arguments and no return values results.

When the program executes, after initializations the first function called (the entry-point of the application) will be main.main(). The program exits immediately and successfully when main.main returns.

The code in functions (the body) is enclosed between braces: { }.

Schematically, a general function looks like:

func functionName(param1 type1, param2 type2, ...) (ret1 type1, ret2 type2, ...) {
    // ...
}
Function names
fmt.Print and fmt.Println

Printing a string or a variable can be done even simpler with the predefined functions print and println. For example,

print("ABC")
println("ABC")
println(i)

These are only to be used in the debugging phase; when deploying a program replace them with their fmt relatives.

Comments

Comments are not compiled. They are used by godoc.

[TWTG p56]

Every package should have a package comment, a block comment immediately preceding the package statement. A package can be spread over many files, but the comment needs to be in only one of them. This comment is shown when a developer demands info of the package with godoc.

Types

A declaration of a variable with var automatically initializes it to the zero-value defined for its type. A type defines the set of values and the set of operations that can take place on those values.

Types can be:

A structured type which has no real value (yet) has the value nil, which is also the default value for these types (in C anc C++ it is NULL).

Use the keyword type for defining your own type (usually a struct type). It is also possible to define an alias for an existing type, for example:

type (
    IZ int
    FZ float
    STR string
)

Program structure

gotemplate.go is an example of the general structure of a Go program. This structure is not necessary, the compiler does not mind if main() or the variable declarations come last, but a uniform structure makes Go code better readable from top to bottom:

Order of execution
  1. All packages in package main are imported in the order as indicated.
  2. In every package, if it imports packages, Step 1 is called for this package (recursively) but a certain package is imported only once.
  3. For every package (in reverse order of dependencies) all constants and variables are evaluated, and the init() if it contains this function.
  4. In package main the same happens, and then main() starts executing.

Conversions

A value can be converted (cast, coerced) into a value of another type. Go never does implicit (automatic) conversion, it must be done explicitly with the syntax like a function call, as in valueOfTypeB = typeB(valueOfTypeA). For example:

a := 5.0
b := int(a)

This can only succeed in certain well defined cases (from a narrower type to a broader type, for example: int16 to int32). When converting from a broader type to a narrower type (for example: int32 to int16, or float32 to int) loss of value (truncation) can occur. When the conversion is impossible and the compiler detects this, a compile-error is given, otherwise a runtime-error occurs.

Variables with the same underlying type can be converted into one another:

var a IZ = 5
c := int(a)
d := IZ(c)

Naming

[TWTG p60]

Constants

A constant (const) contains data which does not change. This data can only be of type boolean, number (integer, float or complex) or string. It is defined with the format const identifier [type] = value (type specifier [type] is optional, the compiler can implicitly derive the type from the value). For example:

const b string = "abc"
const Pi = 3.14159

A value derived from an untyped constant becomes typed when it is used within a context that requires a typed value. For example:

var n int
f(n + 5) // untyped numeric constant 5 becomes typed as int

Constants must be evaluated at compile time; a const can be defined as a calculation, but all the values necessary for the calculation must be available at compile time. For example:

const c1 = 2/3 // ok
const c2 = getNumber() // gives the build error: getNumber() used as value

Constants can be used for enumerations:

const (
    Unknown = 0
    Female = 1
    Male = 2
)

In such cases, the value iota can be used to enumerate the values:

const (  // iota is reset to 0
    c0 = iota  // c0 == 0
    c1 = iota  // c1 == 1
    c2 = iota  // c2 == 2
)

This can be shortened to:

const (
    c0 = iota
    c1
    c2
)

[TWTG p62-63]

See Constant declarations and iota.

Variables

The general form for declaring a variable is var identifier type. The type is written after the identifier of the variable, contrary to almost any other programming language. Why did the Go designers chose for this convention? It removes some ambiguity which can exist in C declarations, for example, in int* a, b;, only a is a pointer and b is not; in Go, they can both be declared pointers as follows: var a, b *int.

Variables can be declared using the following format:

var a int
var b bool
var str string

This can be also written as (mainly used to declare variables globally):

var (
    a int
    b bool
    str string
)

When a variable is declared it contains automatically the default zero value for its type: false for booleans, 0 for integers, 0.0 for floats, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. All memory in Go is initialized.

A variable (constant, type, function) is only known in a certain range of the program, called the scope:

Although identifiers have to be unique, an identifier declared in a block may be redeclared in an inner block: in this block (but only there) the redeclared variable takes priority and shadows the outer variable with the same name; if used, care must be taken to avoid subtle errors.

Variables can get their value (which is called assigning and uses the assignment operator =) at compile time, but a value can also be computed or changed during runtime. For example:

a = 15
b = false

In general, a variable b can only be assigned to a variable a as in a = b, when a and b are of the same type.

Declaration and assignment (initialization) can be combined, in the general format var identifier [type] = value. For example:

var a int = 15
var b bool = false

However, the Go compiler is intelligent enough to derive the type of a variable from its value (dynamically, also called automatic type inference, similar to Python and Ruby, but there it happens in run time), so the following forms (omitting the type) are also correct:

var a = 15
var b = false
// Or, equivalently:
var (
    a = 15
    b = false
)

It can still be useful to include the type information in the case where you want the variable to be typed something different than what would be inferred, such as in: var n int64 = 2.

However, an expression (declaration) like var a is not correct, because the compiler has no clue about the type of a.

Variables could also be expressions computed at runtime, like:

var (
    HOME = os.Getenv("HOME")
    USER = os.Getenv("USER")
)

The var syntax is mainly used at a global, package level; in functions it is replaced by the short declaration syntax :=.

Value types and reference types

Memory in a computer is used in programs as a enormous number of words:

All variables of elementary (primitive) types like int, float, bool, string are value types. They point directly to their value contained in memory.

Composite types like arrays and structs are also value types.

When assigning the value of a value type to another variable: j = i, a copy of the original value i is made in memory, as illustrated in the figure below:

Fig 4.2: Assignment of value types

The memory address of the word where variable i is stored is given by &i.

A reference type variable r1 contains the address of the memory location where the value of r1 is stored (or at least the first word of it). This address, called a pointer, is also contained in a word.

The different words a reference type points to could be sequential memory addresses (the memory layout is said to be contiguously) which is the most efficient storage for computation, or the words could be spread around, each pointing to the next.

When assigning r2 = r1, only the reference (the address) is copied, as illustrated in the figure below:

Fig 4.3: Reference types and assignment

If the value of r1 is modified, all references of that value (like r1 and r2) then point to the modified content.

In Go, pointers are reference types, as well as slices, maps and channels. The variables that are referenced are stored in the heap, which is garbage collected and which is a much larger memory space than the stack.

Printing

Short forms of declaration and assignment

Initializing declaration with :=

With the type omitted, the keyword var is pretty superfluous (e.g. var a = 50), so it may be written as a: = 50, and the types of is inferred by the compiler.

a := 50 is the preferred form, but it can only be used inside functions, not in package scope. The := operator effectively makes a new variable; it is also called an initializing declaration.

If after the lines above in the same codeblock we declare a := 20, this is not allowed: the compiler gives the error "no new variables on left side of :="; however a = 20 is ok because then the same variable only gets a new value.

Undeclared and unused variables
Multiple declaration and assignment

With two variables it can be used to perform a swap of the values: a, b = b, a.

The blank identifier _ can also be used to throw away values, like the value 5 in: _, b = 5, 7

_ is in effect a write-only variable, you cannot ask for its value. It exists because a declared variable in Go must also be used, and sometimes you don’t need to use all return values from a function.

The multiple assignment is also used when a function returns more than 1 value, for example: val, err = func1(var1).

Structs

Visibility

The naming of the struct type and its fields adheres to the visibility rule. It is possible that an exported struct type has a mix of fields: some exported, others not.

Factory methods

Force using factory methods on a private type [TWTG p233]:

wrong := new(matrix.matrix)    // will NOT compile (matrix is private)
right := matrix.NewMatrix(...)   // the ONLY way to instantiate a matrix

Structs with tags

Only the package reflect can access tag content. reflect.TypeOf() on a variable gives the right type; if this is a struct type, it can be indexed by Field, and then the Tag property can be used. For example:

Anonymous fields and embedded structs

Conflicting names [TWTG p239]

  1. An outer name hides an inner name. This provides a way to override a field or method.
  2. If the same name appears twice at the same level, it is an error if the name is used by the program.

Methods

Methods on embedded types and inheritance

Embed functionality in a type

  1. Aggregation (or composition): include a named field of the type of the wanted functionality, embed_func1.go
  2. Embedding: embed_func2.go

Format specifiers

String()-method on a type [TWTG p259]:

Interfaces

Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here.

The interface variable both contains the value of the receiver instance and a pointer to the appropriate method in a method table.

Interface embedding interfaces

An interface can contain the name of one or more other interface(s), which is equivalent to explicitly enumerating the methods of the embedded interface in the containing interface. [TWTG p270]

Detect and convert the type of an interface variable: type assertions

We can test if varI (interface variable) contains at a certain moment a variable of type T with the type assertion test [TWTG p271]:

if v, ok := varI.(T); ok {
    // checked type assertion
}

The type switch

type_switch.go

Testing if a value implements an interface

v is a value and we want to test whether it implements the Stringer interface:

if sv, ok := v.(Stringer); ok {
    fmt.Printf("v implements String(): %s\n", sv.String()); // note: sv, not v
}

Writing functions so that they accept an interface variable as a parameter makes them more general. Use interfaces to make code more generally applicable.

Variables of interface type

A variable of interface type stores a pair: the concrete value assigned to the variable, and that value's type descriptor.

Using method sets with interfaces

  1. Pointer methods can be called with pointers.
  2. Value methods can be called with values.
  3. Value-receiver methods can be called with pointer values because they can be dereferenced first.
  4. Pointer-receiver methods cannot be called with values, however, because the value stored inside an interface has no address.

Examples:

Empty Interface

A variable of empty interface type interface{} can through assignment receive a variable of any type.

Interface Slice

Interface to interface

An interface value can also be assigned to another interface value, as long as the underlying value implements the necessary methods.

twtg_11.9.5.go

Reflection

Reflection is the ability of a program to examine its own structure, particularly through the types; it’s a form of metaprogramming. reflect can be used to investigate types and variables at runtime, e.g. its size, its methods, and it can also call these methods "dynamically".

twtg_11.10.1.go

Example:

Setting a value through reflection

Reflection on structs

Printf and reflection

Printf uses the reflection package to unpack it and discover the argument list, print.go

Interfaces and dynamic typing

Summary of object-orientedness of Go

[TWTG p306]

Higher order functions

[TWTG p306-309]


References