Go Language

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

Internal

Overview

Go Language Specification document defines Go as a general-purpose compiled language designed with systems programming in mind. It is statically and strongly typed and garbage-collected and has explicit support for concurrent programming. Programs are constructed from packages, whose properties allow efficient management of dependencies. The existing implementations use a traditional compile/link model to generate executable binaries.

Go declarations can be read "naturally" from left to right, which makes it easy to read.

These are several reasons to use Go:

Printing

Use println():

println("something")

Also see:

Printing to stdout and stderr in Go

Comments

C-style comments, dropped by the compiler:

// This is a comment.
var x int // this is another comment

Block comments:

/* 
    This is a 
    block comment.
*/

Comments should be complete sentences and end with a period.

Every exported package member, and the package itself should have a comment. A comment immediately preceding the package declaration is considered the documentation comment for the package as a whole. Longer package comments may warrant a file of their own. The file is usually called doc.go.

The first sentence of the comment should start with the name being exported and provide a summary of it. Function parameters and other identifiers are mentioned without quotation or markup.

Also see:

Go Style

Whitespace

Whitespace in Go are carriage returns (u+000D), newlines (u+000A), spaces (u+0020) and tabs (u+0009).

Keywords

https://go.dev/ref/spec#Keywords

Keywords, or reserved words, can only be used to mean the thing Go expects them to mean. They cannot be used as identifiers: variable names, function names, or as names of other constructs.

import package range
var if else
type struct interface
func return defer
go chan select

Also see:

Variables, Parameters, Arguments | Keywords

Identifiers (Names)

Names start with a letter. They can have any number of letters, digits and underscores. They are case sensitive, and their spelling and capitalization must be different of that of the keywords of the language.

Go style recommends camel-case for identifiers.

Go Naming Style

The Blank Identifier (_)

https://go.dev/ref/spec#Blank_identifier

_ is the blank identifier. The blank identifier is used to tell the compiler that we want to ignore one return value of a function, including errors, when we're only interested in others, that we don't need a variable or that we want to import a package that is not directly used.

The blank identifier can never be referenced.

Pre-Declared Identifiers

https://go.dev/ref/spec#Predeclared_identifiers

Pre-declared identifiers are available by default, without needing to import anything. They are implicitly declared in the universe block.

Pre-Declared Zero Value

nil

Pre-Declared Constants

true false iota

Pre-Declared Types

bool string uintptr error
int int8 int16 int32 int64
uint uint8 (byte) uint16 uint32 (rune) uint64
float32 float64 complex64 complex128

The source code of these types is available in $GO_HOME/src/builtin/builtin.go.

Pre-Declared Functions

append() cap() close() complex() copy()
delete() imag() len() make() new()
panic() print() println() real() recover()

TODO: when the similar table from Go_Functions#Built-in_Functions is fully updated, copy it here for convenience.

Also see:

Built-in Functions

Exported Identifiers

https://go.dev/ref/spec#Exported_identifiers

An identifier must be exported to permit access to it from another package. An identifiers is exported if the first character of the identifier's name is a Unicode uppercase letter AND the identifier is declared in the package block or it is a field name or a method name. For more details about package encapsulation see:

Packages as Encapsulation Mechanism

Blocks

https://go.dev/ref/spec#Blocks

A block is a contiguous sequence of declarations and statements. They are also known as lexical blocks. They can be enclosed in matching curly brackets, in which case they are called explicit blocks, or not, and in that case they're called implicit blocks.

Blocks are hierarchical: blocks can be enclosed in other, larger blocks, recursively.

Blocks are involved when the scope of a variable is determined. Each block can have its own variables, and the visibility rules for a specific variable are defined in relation to blocks.

Explicit Block

An explicit block is a sequence of statements enclosed within curly brackets.

{
   // block
   ...
}

A function's body is defined as an explicit block.

Implicit Block

There are blocks implicitly defined without curly brackets. From the "largest" to "smaller" implicit blocks, there are the Universe block, Package blocks, File blocks and others.

Universe Block

All Go code in existence.

Package Block

Each package has a package block, which contains all the code in the package. All identifiers from the package block whose names start with an uppercase letter are exported. It has been suggested that the package global state, which includes all package-level variables, should be kept to a minimum, and avoided altogether if possible. See Package Global State for more details.

File Block

All Go source code in a single file.

Source File Name Conventions

  • File names are all lowercase and words are separated by underscores (ex. "resource_manager.go")
  • File names that begin with "." or "_" are ignored by the go tool.
  • Files with the suffix _test.go are only compiled and run by the go test tool.
  • Files with os and architecture specific suffixes automatically follow those same constraints, e.g. name_linux.go will only build on linux, name_amd64.go will only build on amd64. This is the same as having a //+build amd64 line at the top of the file.

if, for, switch Blocks

switch and select Clauses Blocks

Variables

Variables are names associated with memory locations that store values. Variable name rules are described in the Identifiers section. Each variable has a type. Because Go is a statically typed language, the type associate with a variable may not change after declaration.

Each variable defined in Go occupies a unique memory location. It is not possible to have two variables that share the same storage location. It is possible to have two variable that point to the same storage location, in case of pointer variables, but this is not the same thing as sharing the same storage location. In other words, Go has no reference variables.

(No) Reference Variables

Go does not have reference variables. Each variable defined in Go occupies a unique memory location, which contains the value of the variable. Maps, channels, etc. variables are all values, not references. As such, there is no pass-by-referece in Go, everything is passed by value. To achieve behavior similar to pass-by-reference, use pointers. This is explained here:

Pass by Value vs. Pass by Reference vs. Pass by Pointer in Go

Variable Declaration

The name and the type of the variable are specified in the variable declaration, which is a statement prefixed by the keyword var. This style is some times referred to as "long declaration":

var <variable-name>[, <variable-name-2>, ...]  <type>
var size int
var size, weight int

Such a variable declaration is also an implicit initialization with the zero value of the variable's type. Unless you intent to initialize the variable to a non-zero value with the short variable declaration := operator can be used, var var_name var_type is the preferred syntax.

More variables can be declared at the same time with:

var (
  a int
  b string
  ...
)

A variable that can take any type can be declared using interface{} or its equivalent any:

var r any

Also see:

Variables, Parameters, Arguments

Variable Names

For rules and conventions to use when naming variables, see Identifiers (Names) above. For special rules concerning pointer variable names, see:

Pointers in Go | Pointer Variable Names

Variable Initialization after Declaration

var color string
color = "blue"

Variable Initialization in Declaration

Variables can be initialized in the declaration:

var color string = "blue"

This is redundant, and some editors will render the type name in gray and pop up a soft static analysis warning: "Type can be omitted", because an alternative syntax, initialization with type inference is available.

Variable Initialization with Type Inference

var color = "blue"

Idiomatic Variable Naming Conventions

Variable names should be short rather than long. The closer to declaration a name is used, the shorter it should be. In other words, the farther away from the declaration you use it, the longer the name should be. i is fine when iterating over an array. Two letter variable names should be used for slices or maps.

var tt []*Thing // tt is many

Since references include package names, variable names should not be prefixed with the package name. If the package name is xml, use the name Reader, not XMLReader. In the second case, the full name would be xml.XMLReader.

Also see:

Go Style

Swap Variable Values

a, b = b, a

Also works with arrays and slices.

Short Variable Declaration

The declaration and initialization can be performed together with the := operator. This obviates the need for the var keyword and it deploys type inference. This kind of declaration can only be performed inside a function.

color := "blue"

The short variable declaration can be used to initialize more than one variable at the same time, when the assignment contains multiple values, or when on the right side of the assignment there is a function that returns multiple values:

i, j := 0, 1
color, size := someFuncWithTwoReturnValues(...)

Variable Declaration with new()

new() creates a variable or a certain type, specified as the argument of the new() function invocation, and returns a pointer pointer to that variable.

ptri := new(int)
*ptri = 3

Why do we need to involve variables here? There's no variable, new() just allocates space for an instance of a certain type and returns a pointer to it.

Creating instances with New()

Go New()

Scope

The scope of a variable is defined as places in the code where the variable can be accessed. In Go, the scope of a variable can be formally defined by using the concept of block: Go is a lexically scoped language using blocks.

Given two blocks bi and bj, we say that bi ≥ bj if bj is inside (is enclosed in) bi. This relationship is transitive.

The formal definition of visibility (or accessibility) of a variable v is: a variable v is visible in a block bj if it is declared in a block bi so bi ≥ bj.

If a variable with the same name is declared in two blocks between which there is a an inclusion relationship, the variable defined in the "closest" block takes precedence.

Also see:

Environment of a Function

Variable Deallocation

Memory Management and Garbage Collection | Variable Deallocation

Constants

https://go.dev/ref/spec#Constants

A constant is a typed expression whose value is known at compile time, and cannot be changed once declared. The compiler detects modification attempts and throws a compilation error. The type is inferred from the right-hand side of the assignment. Multiple constants can be assigned at the same time using enclosing parentheses.

const <constant_identifier> [constant_type] = <initial_value_literal>
const a = 1
const (
	b = 1.1
	c = "something"
)

Constant Naming Conventions

Other languages name their constants with upper caps. In Go, an unexported constant is "maxLength", not "MaxLength" or "MAX_LENGTH".

Enumerations

Go does not have formal enums, but the language allows for sets of related, yet distinct constants.

Go Enumerations

Type

https://go.dev/ref/spec#Types

A Go type is similar to types from other languages, in that it defines the range of values and the operations that can be performed with all those values. For more details, see:

Programming Languages Concepts | Types

Go is a strongly and statically typed language with no implicit conversions. This gives Go a stronger type safety than Java, which as implicit conversions, but the code reads more like Python, which has untyped variables.

To deplete Go Concepts - The Type System

The Primitive vs. Non-Primitive Nature of Types

A type is said to have a primitive nature if adding something to, or removing something from a value of that type creates a new value. When passing values of these types to functions, copies the arguments are made inside the function. Integers, floats, booleans and strings are primitive types. Primitive types are some times referred to as data types. The pointers themselves, but not, in general, the instances the pointers point to, are also a primitive type. The Go language specification says that pointers are a composite type.

For non-primitive types, adding or changing something to the value of the type does not create a new value, it instead mutates the existing value. The value itself is changed, while various references in existence point to the mutated value. When such a value is passed to a function, no copy of the value is made, a reference to it is passed instead. When a factory function returns a pointer, it's a good indication that the nature of the value being returned is non-primitive.

Structs can represent data values that could have either a primitive or a non-primitive value. For a discussion on this subject, see Structs below.

More details: Go in Action Section 5.3 "The Nature of Types".

Also see:

Programming Languages Concepts | The Primitive vs. Non-Primitive Nature of Types

Type Declaration

Types are declared using the keyword type. The type keyword is also used for declaring structs and interfaces.

Type Alias

Go allows defining a type alias, or alternate name, for a type. A type declaration is a statement that begins with the keyword type:

type Size int

Two different aliases of the same type count as two different types.

Primitive (Data) Types

Pointers

Pointers in Go

String

Go Strings

Numbers

https://go.dev/ref/spec#Numeric_types

Integer

Go Integers

Floating Point

Go Floating Point

Booleans

https://go.dev/ref/spec#Boolean_types

Arrays

In Go, arrays have characteristics of a primitive data type, they are handled as values, and they are passed to function by value. Arrays are at the same time composite types. For more details, see:

Go Arrays | Arrays are Values

Composite Types

Go composite types aggregate other data types. In Go, the composite data types are arrays, slices, maps, structs, pointers, functions, interfaces and channels.

Arrays

Go Arrays

Slices

Go Slices

Maps

Go Maps

Functions

Functions

Channels

Channels

type-Introduced Types

Structs

Structs

Interfaces

Interfaces

Reference Type

TODO: Go_Concepts_-_The_Type_System#Reference_Types

Type Conversion

Built-in functions are available to do type conversions. The general syntax is:

newTypeVar = <type-name>(oldTypeVar)
var x int32 = 1
var y int16 = 2
// println(x + y) // compilation will fail on this, cannot apply + on two different types
z := int32(y)
println(x + z)

Zero Value

0 for int, the empty string "" for string.

One of the elements of Go philosophy is to make the zero value useful. A "zero value" structure should be ready to use and useful. Examples are the byte buffer or sync.Mutex where you can just put one of these types inside of your structure, not even allocate it or declare a variable of that type, and just use it. You don't have to call an init function, or think about it. This is even more valuable when you build complex data structures that have these things inside them, because if you build the structure so zero value is meaningful, byte buffer or sync.Mutex does not break that.

Pass by Value vs. Pass by Reference vs. Pass by Pointer in Go

Go Functions | Pass by Value vs. Pass by Reference vs. Pass by Pointer

Concrete vs. Interface Types

A concrete type is a regular type that is fully specified: it specifies the exact representation of its data, and for every method that declares it as its receiver type, there is implementation. On the other hand, an interface type is the type resulted from declaring an interface. The interface type does not have data, nor method implementations.

An interface type gets mapped to a concrete type.

Type Assertions

Type Assertions

Expressions

operators combine operands into expressions.

Operators

* Multiplication, Pointer Dereference * has several meanings in Go:
  - Multiplication operator.
  - Dereferencing operator. For more details see Dereferencing Operator.
  - * also designates pointer types and pointer array types.
[] Indexing operator Used with strings, arrays, slices and maps.
<- Arrow operator Used to send and receive data to/from a channel.

Operator Precedence

The operators with the highest precedence appear at the top:

Category Operator Associativity
Postfix
()   []   ->   .   ++   --   
Left to Right
Unary
+   -   !   ~   ++   --   (type)   *   &   sizeof   
Right to left
Multiplicative
*   /   %   
Left to right
Additive
+   -   
Left to right
Shift
<<   >>   
Left to right
Relational
<   <=   >   >=   
Left to right
Equality
==   !=   
Left to right
Bitwise AND
&   
Left to right
Bitwise XOR
^   
Left to right
Bitwise OR
|   
Left to right
Logical AND
&&   
Left to right
Logical OR
||   
Left to right
Assignment
=   +=    -=   *=   /=   %=    >>=    <<=    &=   ^=   |=   
Right to Left
Comma
,   
Left to right

Control Flow

Control flow defines the order in which statements are executed in a program. The control flow can be changed by using control structures.

If Statement

The if statement specifies the conditional execution of two branches according to the value of a boolean expression. Optionally, the boolean expression may be preceded by another statement, which is executed before the expression is evaluated.

Simple If

https://go.dev/ref/spec#If_statements
if <condition> {
  <statements>
}
if x > 5 {
  println(x)
}

If/else

if <condition> {
  <statements>
} else {
  <statements>
}
if x > 5 {
  println(x)
} else {
  println("something else")
}

A special if syntax supports the Go error handling idiom that relies on functions returning errors as result value:

var result ...
var err error
if result, err = someFunc(); err != nil {
  // handle error
  return
}
// handle success
...

Avoid else in the idiom above. Do NOT write this:

if result, err := someFunc(); err {
  // handle error
  ...
} else { // BAD, avoid the "else"
  // handle success
  ...  
}

If/else if/else

if <condition> {
  <statements>
} else if <condition> {
  <statements>
} else {
  <statements>
}
if x < 5 {
  println(x)
} else if x == 5 {
  println("is 5")
} else {
  println("something else")
}

For Loops

The most generic syntax of the for statement is:

for <init>; <condition>; <update> {
  <statements>
}

The for loop executes the init statement and then iterates while the condition evaluates to true. The condition is an expression evaluated on each iteration. condition must evaluate to a boolean. If the expression evaluates to true, the block is executed. At the end of each block execution, the update statement is executed.

The init statement is usually a variable declaration and initialization. Multiple variables can be declared at the same time (for more details, see Short Variable Declaration section above):

for i, j := 0, 0; ... {
  ...
}

Examples:

for i := 0; i < 3; i ++ {
  println(i)
}

All three expressions (initialization, condition and update) are optional:

i := 0
for i < 3 {
  println(i)
  i ++
}
i := 0
for  {
  if i == 3 {
    break
  }
  println(i)
  i ++
}

To iterate over an iterable structure, use range:

ss := []string{"a", "b", "c"}
for index, value := range ss {
  ...
}

If the values of the iterable structure are not important, and only the number of elements matters, this syntax can also be used:

ss := []string{"a", "b", "c"}
for range ss {
  ...
}

for can be used to iterate over values received from a channel.

Break

break exits the closest enclosing loop.

Continue

continue skips the rest of the current iteration.

Switch

Tagless Switch

Error Handling

Error Handling

Modularization

In Go, programs are constructed from packages. More details:

Go Modularization

Object Oriented Programming

Object Oriented Programming

Concurrency

Go Concurrency

Memory Management and Garbage Collection

Memory Management and Garbage Collection

Extension

What is an extension?

Assertions

Go does not have assertions. This is why: https://golang.org/doc/faq#assertions

A crude equivalent would be:

if condition {
  panic(...)
}

Compilation

Go compiler is fast, for the following reasons:

  1. All imports must be explicitly listed at the beginning of each source file, so the compiler does not have to read and process the entire file to determine its dependencies.
  2. The dependencies of a package for a directed acyclic graph - this is enforced - and because there are no cycles, the packages can be compiled separately following a topological sort, possibly in parallel.
  3. The object file for a compiled Go package records export information not for just the package itself, but for all its dependencies. When compiling a package, the compiler must read one object file for each import, but need not look beyond these files.

Cross-Compilation

Cross-Compilation Mac to Linux

Build Tags

Special comments called build tags give more control over compilation

// +build linux darwin

For more details on build constraints, see:

go doc go/build

Object File

The result of the compilation of all source files that constitute a package is stored in an object file. Each package has one object file. Object files have an .a extension. Package object files can be created and locally installed with go install. If the linker detects a main package, all interdependent object file are linked to produce the executable.

Incremental Builds

An incremental build is a Go compiler feature that allows it to build only the packages that have been changed or are affected by changes, instead of recompiling the entire project code base every time the project is compiled. This behavior is implemented by computing source file hashes and associating them with timestamps. The hashes and the object files produced by builds are stored in a build cache.

Build Cache

Go Build Cache

Executable

Executables and the "main" Package

Environment Variables

Go Environment Variables

Annotations

Go does not have support for annotations. Somewhat similar, but with a much more limited scope are the struct tags.

Go Code Generation

https://go.dev/blog/generate

Identity, Equality, Comparability

The Go notion of comparability implies that the equality operators == and != must return the correct values when used.

Generics

Go Generics

Code Examples

Go Code Examples