Let's Go
---
How to Write Go Code
Go programs are organized into packages.
A package is a collection of source files in the same directory that are compiled together.
A module is a collection of related Go packages that are released together.
A Go repository typically contains only one module, located at the root of the repository.
A file named go.mod there declares the module path: the import path prefix for all packages within the module.
Each module’s path not only serves as an import path prefix for its packages, but also indicates where the go command should look to download it. For example, in order to download the module golang.org/x/tools, the go command would consult the repository indicated by https://golang.org/x/tools.
Packages in the standard library do not have a module path prefix.
|
|
|
|
go install command builds the hello command, producing an executable binary. It then installs that binary as $HOME/go/bin/hello.
|
|
The install directory is controlled by the GOPATH and GOBIN environment variables.
The go mod tidy command adds missing module requirements for imported packages and removes requirements on modules that aren’t used anymore.
Go has a lightweight test framework composed of the go test command and the testing package.
You write a test by creating a file with a name ending in _test.go that contains functions named TestXXX with signature func (t *testing.T). The test framework runs each such function; if the function calls a failure function such as t.Error or t.Fail, the test is considered to have failed.
|
|
|
|
go build
- Compiles packages and their deps.
- Library packages: compile into build cache only (no visible files).
- Single command package: produces a binary in the current directory (unless
-ois used). - Multiple packages (e.g., ./…): compile into cache only (no binaries written).
When:
- You want a one-off binary at a specific path/name.
- CI “compile check” without installing.
- Cross-compiling.
|
|
go install
- Compiles and installs command packages to
GOBIN(or$GOPATH/binifGOBINnot set). - For library packages, nothing visible (just cache).
When:
- You want a tool on your
PATHfor long-term use. = Installing a remote tool by version.
|
|
go run
- Compiles and runs a command package; does not keep the binary (beyond cache).
When:
- Quick experiments, scripts, demo programs.
|
|
A Tour of Go
-
Every Go program is made up of packages.
-
Programs start running in package
main. (alsofunc main()) -
By convention, the package name is the same as the last element of the import path, such as
randfrommath/rand. -
mathis both:- a real package you can import (
math.Sqrt,math.Pi), and - a parent directory used to group related packages like
math/rand,math/big, etc. That grouping is purely organizational (docs, repo layout).math/randdoes not live “inside” themathpackage — its actual package name is justrand.
- a real package you can import (
-
This code groups the imports into a parenthesized, factored import statement. Constant, Variables have similar factored statement.
|
|
-
In Go, a name is exported if it begins with a capital letter.
-
A function can take zero or more arguments. Notice that the type comes after the variable name.
- https://go.dev/blog/declaration-syntax
- C:
int (*fn)(int[]);to Go:var fn func([]int) int
-
When two or more consecutive named function parameters share a type, you can omit the type from all but the last.
func add(x, y int) -
A function can return any number of results.
-
Go’s return values may be named. If so, they are treated as variables defined at the top of the function. These names should be used to document the meaning of the return values. A return statement without arguments returns the named return values. This is known as a naked return. Naked return statements should be used only in short functions.
func swap(x, y int)(x, y int) -
The var statement declares a list of variables; as in function argument lists, the type is last.
-
A var declaration can include initializers, one per variable. If an initializer is present, the type can be omitted; the variable will take the type of the initializer.
var x, y int = 1, 2 -
Inside a function, the
:=short assignment statement can be used in place of a var declaration with implicit type. Outside a function, every statement begins with a keyword (var, func, and so on) and so the:=construct is not available.c, python, java := true, false, "no!"
basic types
- Basic types are:
|
|
-
Generic:
int,uint- Size = machine word (32-bit on 32-bit CPUs, 64-bit on 64-bit CPUs).
- Fastest for counters, indexing, lengths (len, cap, range all use int).
-
Fixed-width: int8/16/32/64, uint8/16/32/64
- Size is guaranteed, independent of platform.
- Use when binary compatibility or exact range matters.
-
Aliases:
byte=uint8,rune=int32(for Unicode code points). -
Pointer-sized int:
uintptr- Unsigned integer large enough to hold a pointer value; only for low-level/unsafe interop.
-
General app logic:
int -
Binary I/O / network / storage: exact u/int{8,16,32,64} + explicit endianness
-
Large counters / time / sizes:
int64/uint64 -
Bytes vs text:
[]bytefor raw bytes,runefor Unicode code points (runeis likecharin other languages; for ASCII, usebyte; for Unicode(UTF-8, UTF-16, UTF-32), userune)
|
|
-
Unsigned types: only when you truly need unsigned semantics/bitmasks
-
uintptr: only for unsafe/syscall bridging, convert back immediately
-
The zero value is:
- 0 for numeric types,
- false for the boolean type, and
- "" (the empty string) for strings.
-
The expression T(v) converts the value v to the type T.
|
|
- Constants are declared like variables, but with the
constkeyword.
for
- The basic
forloop has three components separated by semicolons:- the init statement: executed before the first iteration
- the condition expression: evaluated before every iteration
- the post statement: executed at the end of every iteration
- The init and post statements are optional.
- For is Go’s “while”
|
|
if
|
|
switch
- Go’s switch is like the one in C, C++, Java, JavaScript, and PHP, except that Go only runs the selected case, not all the cases that follow. In effect, the break statement that is needed at the end of each case in those languages is provided automatically in Go.
- Another important difference is that Go’s switch cases need not be constants, and the values involved need not be integers.
|
|
- Switch cases evaluate cases from top to bottom, stopping when a case succeeds.
- Switch without a condition is the same as switch true. This construct can be a clean way to write long if-then-else chains.
|
|
defer
- A
deferstatement defers the execution of a function until the surrounding function returns. - Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.
- https://go.dev/blog/defer-panic-and-recover
- Defer is commonly used to simplify functions that perform various clean-up actions.
- A deferred function’s arguments are evaluated when the defer statement is evaluated.
- Deferred function calls are executed in Last In First Out order after the surrounding function returns.
- Deferred functions may read and assign to the returning function’s named return values.
panicis a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller.recoveris a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.
|
|
pointer
- Go has pointers. A pointer holds the memory address of a value.
- The type
*Tis a pointer to aTvalue. Its zero value isnil. - Unlike C, Go has no pointer arithmetic.
|
|
struct
- A struct is a collection of fields.
- Struct fields are accessed using a dot.
|
|
- Struct fields can be accessed through a struct pointer. To access the field X of a struct when we have the struct pointer p we could write
(*p).X. However, that notation is cumbersome, so the language permits us instead to write justp.X, without the explicit dereference. - A struct literal denotes a newly allocated struct value by listing the values of its fields. You can list just a subset of fields by using the
Name: syntax. And the order of named fields is irrelevant.
|
|
array/slice
-
The type
[n]Tis an array of n values of typeT. -
The type
[]Tis a slice with elements of typeT. -
A slice is formed by specifying two indices, a low and high bound, separated by a colon:
a[low : high]. This selects a half-open range which includes the first element, but excludes the last one.
|
|
- A slice does not store any data, it just describes a section of an underlying array. Changing the elements of a slice modifies the corresponding elements of its underlying array. Other slices that share the same underlying array will see those changes.
- A slice literal is like an array literal without the length.
|
|
- When slicing, you may omit the high or low bounds to use their defaults instead. The default is zero for the low bound and the length of the slice for the high bound.
- A slice has both a length and a capacity. The length of a slice is the number of elements it contains.The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice. The length and capacity of a slice s can be obtained using the expressions
len(s)andcap(s). - A nil slice has a length and capacity of 0 and has no underlying array.
- Slices can be created with the built-in make function; this is how you create dynamically-sized arrays.
|
|
|
|
- Growing slices
|
|
|
|
- Re-slicing a slice doesn’t make a copy of the underlying array. The full array will be kept in memory until it is no longer referenced. Occasionally this can cause the program to hold all the data in memory when only a small piece of it is needed.
|
|
range
- The
rangeform of the for loop iterates over a slice or map. When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.
|
|
map
- A map maps keys to values.
- The zero value of a map is
nil. Anilmap has no keys, nor can keys be added. - The
makefunction returns a map of the given type, initialized and ready for use.
|
|
function/closure
- Functions are values too. They can be passed around just like other values.
- Go functions may be closures. A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables; in this sense the function is “bound” to the variables.
|
|
method
- Go does not have classes. However, you can define methods on types.
- A method is a function with a special receiver argument.
- The receiver appears in its own argument list between the func keyword and the method name.
|
|
- You can declare a method on non-struct types, too.
- You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).
|
|
- You can declare methods with pointer receivers.
- This means the receiver type has the literal syntax
*Tfor some type T. (Also, T cannot itself be a pointer such as *int.) - Methods with pointer receivers can modify the value to which the receiver points. Since methods often need to modify their receiver, pointer receivers are more common than value receivers.
|
|
- Methods and pointer indirection: For the statement
v.Scale(5), even thoughvis a value and not a pointer, the method with the pointer receiver is called automatically. That is, as a convenience, Go interprets the statementv.Scale(5)as(&v).Scale(5)since the Scale method has a pointer receiver. - There are two reasons to use a pointer receiver.
- The first is so that the method can modify the value that its receiver points to.
- The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct.
- In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.
interface
- An interface type is defined as a set of method signatures.
- A type implements an interface by implementing its methods. There is no explicit declaration of intent, no “implements” keyword.
|
|
- Under the hood, interface values can be thought of as a tuple of a value and a concrete type:
(value, type). An interface value holds a value of a specific underlying concrete type. Calling a method on an interface value executes the method of the same name on its underlying type. - If the concrete value inside the interface itself is nil, the method will be called with a nil receiver. In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver.
|
|
- Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.
|
|
- The interface type that specifies zero methods is known as the empty interface:
interface{}. An empty interface may hold values of any type. (Every type implements at least zero methods.) - Empty interfaces are used by code that handles values of unknown type.
|
|
type assertion
- A type assertion provides access to an interface value’s underlying concrete value.
t := i.(T). This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t. If i does not hold a T, the statement will trigger a panic. - To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded.
t, ok := i.(T)
|
|
type switch
- A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.
|
|
stringer
- A
Stringeris a type that can describe itself as a string. Thefmtpackage (and many others) look for this interface to print values.
|
|
error
- The
errortype is a built-in interface similar tofmt.Stringer:
|
|
- Functions often return an error value, and calling code should handle errors by testing whether the error equals nil. A nil error denotes success; a non-nil error denotes failure.
io
- The
iopackage specifies theio.Readerinterface, which represents the read end of a stream of data. - The
io.Reader interfacehas aReadmethod.Readpopulates the given byte slice with data and returns the number of bytes populated and an error value. It returns anio.EOFerror when the stream ends.
|
|
image
- Package
imagedefines theImageinterface:
|
|
type parameter
- Go functions can be written to work on multiple types using type parameters. The type parameters of a function appear between brackets, before the function’s arguments.
|
|
-
comparableis a useful constraint that makes it possible to use the == and != operators on values of the type. -
Go also supports generic types. A type can be parameterized with a type parameter, which could be useful for implementing generic data structures.
|
|
goroutine
- A goroutine is a lightweight thread managed by the Go runtime.
go f(x, y, z)starts a new goroutine runningf(x, y, z). The evaluation of f, x, y, and z happens in the current goroutine and the execution of f happens in the new goroutine. - Goroutines run in the same address space, so access to shared memory must be synchronized. The
syncpackage provides useful primitives.
channel
- Channels are a typed conduit through which you can send and receive values with the channel operator,
<-.
|
|
-
Like maps and slices, channels must be created before use:
ch := make(chan int) -
By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
-
Channels can be buffered. Provide the buffer length as the second argument to make to initialize a buffered channel:
ch := make(chan int, 100). -
A sender can close a channel to indicate that no more values will be sent. Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression: after
v, ok := <-ch. ok is false if there are no more values to receive and the channel is closed. -
The loop for
i := range creceives values from the channel repeatedly until it is closed. -
Only the sender should close a channel, never the receiver. Sending on a closed channel will cause a panic.
-
Channels aren’t like files; you don’t usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.
select
- The
selectstatement lets a goroutine wait on multiple communication operations. Aselectblocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready. - The
defaultcase in a select is run if no other case is ready. Use a default case to try a send or receive without blocking.
lock
- Go’s standard library provides mutual exclusion with sync.Mutex and its two methods:
Lock,Unlock. - We can define a block of code to be executed in mutual exclusion by surrounding it with a call to
LockandUnlock. - We can also use
deferto ensure the mutex will be unlocked as in the Value method.
|
|
Effective Go
Effective Go continues to be useful, but the reader should understand it is far from a complete guide. See issue 28782 for context.
Formatting
The gofmt program (also available as go fmt, which operates at the package level rather than source file level) reads a Go program and emits the source in a standard style of indentation and vertical alignment, retaining and if necessary reformatting comments.
- We use tabs for indentation and gofmt emits them by default. Use spaces only if you must.
- Go has no line length limit.
- Go needs fewer parentheses than C and Java: control structures (if, for, switch) do not have parentheses in their syntax. Also, the operator precedence hierarchy is shorter and clearer.
Commentary
Go provides C-style /* */ block comments and C++-style // line comments.
Names
Package name should be good: short, concise, evocative. By convention, packages are given lower case, single-word names; there should be no need for underscores or mixedCaps.
Another convention is that the package name is the base name of its source directory; the package in src/encoding/base64 is imported as “encoding/base64” but has name base64, not encoding_base64 and not encodingBase64.
Long names don’t automatically make things more readable. A helpful doc comment can often be more valuable than an extra long name.
Getters
It’s neither idiomatic nor necessary to put Get into the getter’s name. If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner.
The use of upper-case names for export provides the hook to discriminate the field from the method. A setter function, if needed, will likely be called SetOwner.
Interface names
By convention, one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc.
MixedCaps
Finally, the convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword names.
Semicolons
If the last token before a newline is an identifier (which includes words like int and float64), a basic literal such as a number or string constant, or one of the tokens break continue fallthrough return ++ -- ) }, the lexer always inserts a semicolon after the token. This could be summarized as, “if the newline comes after a token that could end a statement, insert a semicolon”.
Idiomatic Go programs have semicolons only in places such as for loop clauses, to separate the initializer, condition, and continuation elements. They are also necessary to separate multiple statements on a line, should you write code that way.
You cannot put the opening brace of a control structure (if, for, switch, or select) on the next line. If you do, a semicolon will be inserted before the brace, which could cause unwanted effects.
|
|
Control structures
There is no do or while loop, only a slightly generalized for;
switch is more flexible; if and switch accept an optional initialization statement like that of for;
break and continue statements take an optional label to identify what to break or continue;
and there are new control structures including a type switch and a multiway communications multiplexer, select.
If
Since if and switch accept an initialization statement, it’s common to see one used to set up a local variable.
In the Go libraries, you’ll find that when an if statement doesn’t flow into the next statement—that is, the body ends in break, continue, goto, or return — the unnecessary else is omitted.
Redeclaration and reassignment
In a := declaration a variable v may appear even if it has already been declared, provided:
- this declaration is in the same scope as the existing declaration of v (if v is already declared in an outer scope, the declaration will create a new variable §),
- the corresponding value in the initialization is assignable to v, and
- there is at least one other variable that is created by the declaration.
This unusual property is pure pragmatism, making it easy to use a single err value.
|
|
For
|
|
For strings, the range does more work for you, breaking out individual Unicode code points by parsing the UTF-8.
|
|
Go has no comma operator and ++ and – are statements not expressions. Thus if you want to run multiple variables in a for you should use parallel assignment (although that precludes ++ and –).
|
|
Switch
Go’s switch is more general than C’s. The expressions need not be constants or even integers, the cases are evaluated top to bottom until a match is found, and if the switch has no expression it switches on true.
It’s therefore possible—and idiomatic—to write an if-else-if-else chain as a switch.
|
|
There is no automatic fall through, but cases can be presented in comma-separated lists.
|
|
break statements can be used to terminate a switch early. Sometimes, though, it’s necessary to break out of a surrounding loop, not the switch, and in Go that can be accomplished by putting a label on the loop and “breaking” to that label.
|
|
Of course, the continue statement also accepts an optional label but it applies only to loops.
Type switch
A switch can also be used to discover the dynamic type of an interface variable. Such a type switch uses the syntax of a type assertion with the keyword type inside the parentheses.
Functions
Multiple return values
One of Go’s unusual features is that functions and methods can return multiple values. This form can be used to improve on a couple of clumsy idioms in C programs: in-band error returns such as -1 for EOF and modifying an argument passed by address.
Named result parameters
The return or result “parameters” of a Go function can be given names and used as regular variables, just like the incoming parameters. When named, they are initialized to the zero values for their types when the function begins; if the function executes a return statement with no arguments, the current values of the result parameters are used as the returned values.
The names are not mandatory but they can make code shorter and clearer: they’re documentation.
Defer
Go’s defer statement schedules a function call (the deferred function) to be run immediately before the function executing the defer returns. It’s an unusual but effective way to deal with situations such as resources that must be released regardless of which path a function takes to return. The canonical examples are unlocking a mutex or closing a file.
Deferring a call to a function such as Close has two advantages.
- First, it guarantees that you will never forget to close the file, a mistake that’s easy to make if you later edit the function to add a new return path.
- Second, it means that the close sits near the open, which is much clearer than placing it at the end of the function.
The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes.
|
|
Deferred functions are executed in LIFO order.
|
|
Data
Go has two allocation primitives, the built-in functions new and make.
Allocation with new
new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T. In Go terminology, it returns a pointer to a newly allocated zero value of type T.
Since the memory returned by new is zeroed, it’s helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization.
|
|
Constructors and composite literals
An expression that creates a new instance each time it is evaluated.
|
|
Unlike in C, it’s perfectly OK to return the address of a local variable; the storage associated with the variable survives after the function returns.
In fact, taking the address of a composite literal allocates a fresh instance each time it is evaluated, so we can combine these last two lines. return &File{fd, name, nil, 0}
The fields of a composite literal are laid out in order and must all be present. However, by labeling the elements explicitly as field:value pairs, the initializers can appear in any order, with the missing ones left as their respective zero values. Thus we could say return &File{fd: fd, name: name}
As a limiting case, if a composite literal contains no fields at all, it creates a zero value for the type. The expressions new(File) and &File{} are equivalent.
Allocation with make
It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T). The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use.
In contrast, new([]int) returns a pointer to a newly allocated, zeroed slice structure, that is, a pointer to a nil slice value.
|
|
Remember that make applies only to maps, slices and channels and does not return a pointer. To obtain an explicit pointer allocate with new or take the address of a variable explicitly.
Arrays
- Arrays are values. Assigning one array to another copies all the elements.
- In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.
- The size of an array is part of its type. The types
[10]intand[20]intare distinct.
The value property can be useful but also expensive; if you want C-like behavior and efficiency, you can pass a pointer to the array.
|
|
But even this style isn’t idiomatic Go. Use slices instead.
Slices
Slices wrap arrays to give a more general, powerful, and convenient interface to sequences of data. Except for items with explicit dimension such as transformation matrices, most array programming in Go is done with slices rather than simple arrays.
Slices hold references to an underlying array, and if you assign one slice to another, both refer to the same array. If a function takes a slice argument, changes it makes to the elements of the slice will be visible to the caller, analogous to passing a pointer to the underlying array.
|
|
The idea of appending to a slice is so useful it’s captured by the append built-in function.
|
|
We must return the slice afterwards because, although Append can modify the elements of slice, the slice itself (the run-time data structure holding the pointer, length, and capacity) is passed by value.
- When you pass a slice to a function, the function gets a copy of the slice header (pointer + len + cap), but both headers point to the same underlying array.
- If the function changes elements like
slice[i] = x, the caller can see it (same array). - If the function changes the slice header (re-slicing, growing len, reallocating a new array), those changes affect only the copy inside the function, not the caller’s slice.
Two-dimensional slices
Go’s arrays and slices are one-dimensional. To create the equivalent of a 2D array or slice, it is necessary to define an array-of-arrays or slice-of-slices, like this:
|
|
Maps
The key can be of any type for which the equality operator is defined, such as integers, floating point and complex numbers, strings, pointers, interfaces (as long as the dynamic type supports equality), structs and arrays.
Slices cannot be used as map keys, because equality is not defined on them.
Like slices, maps hold references to an underlying data structure. If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller.
An attempt to fetch a map value with a key that is not present in the map will return the zero value for the type of the entries in the map.
Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for “UTC” or is that 0 because it’s not in the map at all? You can discriminate with a form of multiple assignment.
|
|
To test for presence in the map without worrying about the actual value, you can use the blank identifier (_) in place of the usual variable for the value.
|
|
To delete a map entry, use the delete built-in function, whose arguments are the map and the key to be deleted. It’s safe to do this even if the key is already absent from the map.
|
|
Printing
The functions live in the fmt package and have capitalized names: fmt.Printf, fmt.Fprintf, fmt.Sprintf and so on. The string functions (Sprintf etc.) return a string rather than filling in a provided buffer.
You don’t need to provide a format string. For each of Printf, Fprintf and Sprintf there is another pair of functions, for instance Print and Println. These functions do not take a format string but instead generate a default format for each argument.
The numeric formats such as %d do not take flags for signedness or size; instead, the printing routines use the type of the argument to decide these properties.
|
|
If you just want the default conversion, such as decimal for integers, you can use the catchall format %v (for “value”); the result is exactly what Print and Println would produce.
For maps, Printf and friends sort the output lexicographically by key.
When printing a struct, the modified format %+v annotates the fields of the structure with their names, and for any value the alternate format %#v prints the value in full Go syntax.
That quoted string format is also available through %q when applied to a value of type string or []byte. The alternate format %#q will use backquotes instead if possible.
Another handy format is %T, which prints the type of a value.
If you want to control the default format for a custom type, all that’s required is to define a method with the signature String() string on the type. For our simple type T, that might look like this.
|
|
Our String method is able to call Sprintf because the print routines are fully reentrant and can be wrapped this way.
Don’t construct a String method by calling Sprintf in a way that will recur into your String method indefinitely. This can happen if the Sprintf call attempts to print the receiver directly as a string, which in turn will invoke the method again. It’s a common and easy mistake to make, as this example shows.
|
|
|
|
We write ... after v in the nested call to Sprintln to tell the compiler to treat v as a list of arguments; otherwise it would just pass v as a single slice argument.
Append
You can’t actually write a function in Go where the type T is determined by the caller. That’s why append is built in: it needs support from the compiler.
|
|
Initialization
Constants
Constants in Go are just that—constant. They are created at compile time, even when defined as locals in functions, and can only be numbers, characters (runes), strings or booleans. Because of the compile-time restriction, the expressions that define them must be constant expressions, evaluatable by the compiler. For instance, 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.
In Go, enumerated constants are created using the iota enumerator. Since iota can be part of an expression and expressions can be implicitly repeated, it is easy to build intricate sets of values.
|
|
Variables
Variables can be initialized just like constants but the initializer can be a general expression computed at run time.
The init function
Finally, each source file can define its own niladic init function to set up whatever state is required. (Actually each file can have multiple init functions.)
init is called after all the variable declarations in the package have evaluated their initializers, and those are evaluated only after all the imported packages have been initialized.
A common use of init functions is to verify or repair correctness of the program state before real execution begins.
Methods
Pointers vs. Values
Methods can be defined for any named type (except a pointer or an interface); the receiver does not have to be a struct.
Value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.
This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded.
Interfaces and other types
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.
A type can implement multiple interfaces. For instance, a collection can be sorted by the routines in package sort if it implements sort.Interface, which contains Len(), Less(i, j int) bool, and Swap(i, j int), and it could also have a custom formatter.
|
|
Instead of having Sequence implement multiple interfaces (sorting and printing), we’re using the ability of a data item to be converted to multiple types (Sequence, sort.IntSlice and []int), each of which does some part of the job. That’s more unusual in practice but can be effective.
Interface conversions and type assertions
Type switches are a form of conversion: they take an interface and, for each case in the switch, in a sense convert it to the type of that case.
|
|
A type assertion takes an interface value and extracts from it a value of the specified explicit type. The syntax borrows from the clause opening a type switch, but with an explicit type rather than the type keyword:
|
|
To guard against that, use the “comma, ok” idiom to test, safely, whether the value is a string:
|
|
If the type assertion fails, str will still exist and be of type string, but it will have the zero value, an empty string.
Generality
Exporting just the interface makes it clear the value has no interesting behavior beyond what is described in the interface. It also avoids the need to repeat the documentation on every instance of a common method.
|
|
Interfaces and methods
Since almost anything can have methods attached, almost anything can satisfy an interface.
One illustrative example is in the http package, which defines the Handler interface. Any object that implements Handler can serve HTTP requests.
|
|
The blank identifier
The blank identifier can be assigned or declared with any value of any type, with the value discarded harmlessly.
|
|
To silence complaints about the unused imports, use a blank identifier to refer to a symbol from the imported package. Similarly, assigning the unused variable fd to the blank identifier will silence the unused variable error. This version of the program does compile.
|
|
Sometimes it is useful to import a package only for its side effects, without any explicit use. For example, during its init function, the net/http/pprof package registers HTTP handlers that provide debugging information. It has an exported API, but most clients need only the handler registration and access the data through a web page. To import the package only for its side effects, rename the package to the blank identifier:
|
|
If it’s necessary only to ask whether a type implements an interface, without actually using the interface itself, perhaps as part of an error check, use the blank identifier to ignore the type-asserted value:
|
|
One place this situation arises is when it is necessary to guarantee within the package implementing the type that it actually satisfies the interface. If a type — for example, json.RawMessage — needs a custom JSON representation, it should implement json.Marshaler, but there are no static conversions that would cause the compiler to verify this automatically. If the type inadvertently fails to satisfy the interface, the JSON encoder will still work, but will not use the custom implementation. To guarantee that the implementation is correct, a global declaration using the blank identifier can be used in the package:
|
|
In this declaration, the assignment involving a conversion of a *RawMessage to a Marshaler requires that *RawMessage implements Marshaler, and that property will be checked at compile time. Should the json.Marshaler interface change, this package will no longer compile and we will be on notice that it needs to be updated.
By convention, such declarations are only used when there are no static conversions already present in the code, which is a rare event.
|
|
Embedding
|
|
A ReadWriter can do what a Reader does and what a Writer does; it is a union of the embedded interfaces.
Only interfaces can be embedded within interfaces.