Go Language: Difference between revisions
Line 366: | Line 366: | ||
==Control Default Format for Type Values with <tt>String()</tt>== | ==Control Default Format for Type Values with <tt>String()</tt>== | ||
{{Internal|Go_String()#Overview|<tt>String()</tt>}} | {{Internal|Go_String()#Overview|<tt>String()</tt>}} | ||
==Named Type== | |||
<font color=darkkhaki>What is a "named" type? Formal definition.</font> | |||
=Expressions= | =Expressions= |
Latest revision as of 21:59, 5 November 2024
External
- The Go Programming Language Specification https://go.dev/ref/spec
- go.dev Documentation https://go.dev/doc/#learning
- Search packages or symbols https://pkg.go.dev
Internal
- Go
- Python Language
- Microservices
- Go Language | Modularization
- Go Language | Functions
- Go Language | Object Oriented Programming
- Go Language | Error Handling
- Go Language | Concurrency
- Go Language | Reflection and Metaprogramming
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:
- It compiles fast, and it runs fast.
- Concurrency is a fundamental part of the language and it is efficient.
- The standard library has almost everything one needs.
- It is a terse language and "feels" dynamically typed, but it compiles straight into machine code.
- It has garbage collection.
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:
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:
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:
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:
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
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.
Also see:
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:
The 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
Pre-declared identifiers are available by default, without needing to import anything. They are implicitly declared in the universe block.
Pre-Declared Zero Value
Pre-Declared Constants
Pre-Declared Types
The source code of these types is available in $GO_HOME/src/builtin/builtin.go
.
Pre-Declared Functions
TODO: when the similar table from Go_Functions#Built-in_Functions is fully updated, copy it here for convenience.
Also see:
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:
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:
if, for, switch Blocks
switch and select Clauses Blocks
Constants
Constants, constants naming conventions and enumerations are discussed in:
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.
Type
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:
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:
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'
Type aliasing allows us to attach behavior (methods) with any type, except pointers and interfaces.
TODO Reconcile Go Type Aliasing.
Primitive (Data) Types
Pointers
Pointer Types vs. Base Types
String
Numbers
Integer
Floating Point
Booleans
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:
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
Slices
Maps
Go does not have sets, but a set can be implemented using a map:
Functions
Channels
type-Introduced Types
Structs
Interfaces
Type Conversion
Built-in functions are available to do type conversions. The general syntax is:
newTypeVar = <TypeName>(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)
Alias types can be converted back and forth with this syntax. Because the alias types are the same if we ignore the type name, it is legal to convert between them. The conversion does not create a new value, it just temporarily acts as though the existing value has a new type.
There are other legal conversions, such as from integer to floating point, that do create a new value.
It is an idiom in Go to convert the type of an expression to access a different set of methods.
Type assertion and type switch are also a form of type conversion.
TODO: Tactical_Typing_in_Go
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
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
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
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.
Control Default Format for Type Values with String()
Named Type
What is a "named" type? Formal definition.
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 | 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 |
Constant Expression
A constant expression is an expression that contains only constant operands and that is evaluated at compile time. 1<<3
is a constant expression, while math.Sin(math.Pi/4)
is not because the function call to math.Sin()
needs to happen at run time.
++, --
++
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:
- a
for
loop - an
if
statement - a
switch
statement and a type switch break
continue
- a multiway communication multiplexer
select
return
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
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.
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
Error Handling
Modularization
In Go, programs are constructed from packages. More details:
Standard Library
Object Oriented Programming
Concurrency
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:
- 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.
- 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.
- 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
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
Executable
Environment Variables
Annotations
Go does not have support for annotations. Somewhat similar, but with a much more limited scope are the struct tags.