The Go Programming Language

This chapter contains language-specific recommendations for Go.

Memory Safety

Go provides memory safety, but only if the program is not executed in parallel (that is, GOMAXPROCS is not larger than 1). The reason is that interface values and slices consist of multiple words are not updated atomically. Another thread of execution can observe an inconsistent pairing between type information and stored value (for interfaces) or pointer and length (for slices), and such inconsistency can lead to a memory safety violation.

Code which does not run in parallel and does not use the unsafe package (or other packages which expose unsafe constructs) is memory-safe. For example, invalid casts and out-of-range subscripting cause panics at run time.

Keep in mind that finalization can introduce parallelism because finalizers are executed concurrently, potentially interleaved with the rest of the program.

Error Handling

Only a few common operations (such as pointer dereference, integer division, array subscripting) trigger exceptions in Go, called panics. Most interfaces in the standard library use a separate return value of type error to signal error.

Not checking error return values can lead to incorrect operation and data loss (especially in the case of writes, using interfaces such as io.Writer).

The correct way to check error return values depends on the function or method being called. In the majority of cases, the first step after calling a function should be an error check against the nil value, handling any encountered error. See Regular error handling in Go for details.

Example 1. Regular error handling in Go
type Processor interface {
	Process(buf []byte) (message string, err error)
}

type ErrorHandler interface {
	Handle(err error)
}

func RegularError(buf []byte, processor Processor,
	handler ErrorHandler) (message string, err error) {
	message, err = processor.Process(buf)
	if err != nil {
		handler.Handle(err)
		return "", err
	}
	return
}

However, with io.Reader, io.ReaderAt and related interfaces, it is necessary to check for a non-zero number of read bytes first, as shown in Read error handling in Go. If this pattern is not followed, data loss may occur. This is due to the fact that the io.Reader interface permits returning both data and an error at the same time.

Example 2. Read error handling in Go
func IOError(r io.Reader, buf []byte, processor Processor,
	handler ErrorHandler) (message string, err error) {
	n, err := r.Read(buf)
	// First check for available data.
	if n > 0 {
		message, err = processor.Process(buf[0:n])
		// Regular error handling.
		if err != nil {
			handler.Handle(err)
			return "", err
		}
	}
	// Then handle any error.
	if err != nil {
		handler.Handle(err)
		return "", err
	}
	return
}

Garbage Collector

Older Go releases (before Go 1.3) use a conservative garbage collector without blacklisting. This means that data blobs can cause retention of unrelated data structures because the data is conservatively interpreted as pointers. This phenomenon can be triggered accidentally on 32-bit architectures and is more likely to occur if the heap grows larger. On 64-bit architectures, it may be possible to trigger it deliberately—it is unlikely to occur spontaneously.

Marshaling and Unmarshaling

Several packages in the encoding hierarchy provide support for serialization and deserialization. The usual caveats apply (see [chap-Defensive_Coding-Tasks-Serialization]).

As an additional precaution, the Unmarshal and Decode functions should only be used with fresh values in the interface{} argument. This is due to the way defaults for missing values are implemented: During deserialization, missing value do not result in an error, but the original value is preserved. Using a fresh value (with suitable default values if necessary) ensures that data from a previous deserialization operation does not leak into the current one. This is especially relevant when structs are deserialized.