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