Go Language: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 321: Line 321:
println(x + z)
println(x + z)
</syntaxhighlight>
</syntaxhighlight>
<font color=darkkhaki>TODO: [[Tactical_Typing_in_Go]]>/font>
==Zero Value==
==Zero Value==



Revision as of 23:30, 30 August 2024

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:

To write Go well, it is important to understand its idioms and its naming, formatting and program construction conventions. A guide to idiomatic Go code is https://go.dev/doc/effective_go.

Printing

Use println():

println("something")

Also see:

Printing to stdout and stderr in Go

Source Code Formatting

Go tooling formats the code automatically to a conventional structure. IDEs like IntelliJ follow the same approach, delegating Go code formatting to gofmt. For more details, see:

gofmt

Note that automatic formatting does not free programmers from the responsibility of using idiomatic coding style, which refers to the name of the packages, functions, variables, idomatic error handling, etc. For more details on style, see:

Go Style

Comments

Comments should be complete sentences and end with a period.

Comments that appear before top-level declarations of variables and functions, exported or not, with no intervening newlines, are considered to document the declaration itself. The first sentence of the comment should start with the name being declared and provide a summary of it. Function parameters and other identifiers are mentioned without quotation or markup.

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. These "doc comments" are the primary documentation for a given Go package or command.

Also see:

Go Style

Line Comments

C-style comments, dropped by the compiler:

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

These are the norm.

Block Comments

/* 
    This is a 
    block comment.
*/

Block comments appear mostly as package comments, but are useful within an expression or to disable large swaths of code.

Whitespace

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

Semicolons

Go formal grammar uses semicolons to terminate statements, but the semicolons do not appear in the source. The lexer uses a simple rule to insert semicolons automatically as it scans, so the source code is mostly free of them: if a newline comes after a token that could end a statement, insert a semicolon. More expansively, if the last token before a newline is an identifier, which includes words like int, a basic literal such as a number or string constant, or one of these tokens: break, continue, fallthrough, return, ++, --, ) and }, the lexer always inserts a semicolon after the token.

Idiomatic Go programs have semicolons only in places as for loop clauses to separate the initializer, condition and continuation elements, and also to separate multiple statements on the same line.

One consequence of the semicolon insertion rule is that you cannot place the opening brace of a control structure on the next line, because a semicolon will be inserted before the brace with could cause unwanted effects:

if a < b
{ // this is wrong
  ...
}

This is also why you cannot have else on a different line:

if a < b {
  ...
}
else { // this does not work
  ...
}

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. Many more semantically-mandated and idiomatic naming rules are provided in:

Go 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 struct 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.

Also see:

Go Style

if, for, switch Blocks

for Block

switch and select Clauses Blocks

Constants

Constants, constants naming conventions and enumerations are discussed in:

Constants

Variables

The dedicated Variables section describes variable declaration, initialization in and after declaration, initialization with type inference and short variable declaration. The section explains that there are no reference variables in Go. It documents the idiomatic variables naming conventions. It also addresses variable declaration with new(), creating instances with New(). It describes how to swap variable values. It discusses variable scope and variable deallocation.

Variables

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.

If more than one related types are to be declared in the same file, they can be grouped together as such:

type (
	// SomeStringAlias is a string alias that does this and that.
	SomeStringAlias string
	// SomeStruct is a struct that does this and that.
    SomeStruct struct { ... }
    ...
)

The Go language doesn’t restrict where you define types. However, it is often a good practice to keep the core types grouped at the top of a file, as shown above.

A common practice from other language is to organize types together in a package called model or types. Go encourages to organize code by the functional responsibility, so rather than creating a model package and declare all entity types in there, the (for example) User type should live in its corresponding hypothetical service package.

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.

An instance of the aliased type can be converted to the type alias by casting:

size := Size(10)

Am attempt to convert nil to the type alias will be singled out as a compilation error, as a value cannot be nil:

size := Size(nil) // Compilation error: cannot convert 'nil' to the type 'Size'

Primitive (Data) Types

Pointers

Pointers in Go

Pointer Types vs. Base Types

Pointer Types vs. Base Types

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

Go does not have sets, but a set can be implemented using a map:

Go Sets

Functions

Functions

Channels

Channels

type-Introduced Types

Structs

Structs

Interfaces

Interfaces

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)

TODO: Tactical_Typing_in_Go>/font>

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.

Variables that declared and not explicitly initialized are implicitly initialized with the zero value of the variable's type.

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 Assertion

Type Assertion

Local Type

A "local" type is a type that is defined in the same package as the target syntactical construct - a function for example. Non-local types cannot be associated with function by passing the non-local type as the function's receiver type.

Reference Type

https://github.com/go101/go101/wiki/About-the-terminology-%22reference-type%22-in-Go

Reference type terminology is controversial. "Reference type" has been removed from the Go specification, so it does not seem indicated to keep using it. "Reference type" has been replaced with "descriptor" to refer to slices.

The intention before using the "reference type" terminology is to indicate that the values of the type are so called "header" values. The header contains pointers to underlying data structures and a set of unique fields that are used to manage those data structures. These fields are initialized by default to their zero values, and when that happens, the corresponding variable is nil. The header values are designed to be copied. The header value contains a pointer, therefore if a copy of the header value is passed, the underlying data structure is intrinsically shared.

Before being removing, "reference type" was used to refer to slices, maps, channels, interfaces and functions.

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.
. Selector operator Used to access struct fields.

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

++, --

++ and -- are statements, not expressions.

Control Flow and Control Structures

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

In all cases, there are no parentheses for the expressions that drive the control structures, and the bodies are blocks: must be always brace-delimited.

for

for Loops

if

The following section explains various types of if/else statements, the if initialization statement and an error handling idiom that uses the if initialization statement.

if Statement

switch

switch Statement

Type Switch

Type Switch

break

break exits the closest enclosing loop. The break statement takes an optional label to identify where to break to.

SomeLabel:
    for ... {
        for ... {
             ...
             break SomeLabel
        }
    }

continue

continue skips the rest of the current iteration. The continue statement takes an optional label to identify where to skip to.

select

select Statement

Error Handling

Error Handling

Modularization

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

Go Modularization

Standard Library

Standard Library

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

Identity, Equality, Comparability in Go

Generics

Go Generics

Reflection and Metaprogramming

Reflection and Metaprogramming in Go

Code Examples

Go Code Examples