blob: b32304e526f94d7ced8bffc71cdabc50014cd88a [file] [log] [blame] [view] [edit]
# Go Rubric
[TOC]
This document lists conventions to follow when writing Go in the Fuchsia Source
Tree. These conventions are a combination of best practices, project
preferences, and some choices made for the sake of consistency.
The conventions described here can be considered as adding or amending common
best practices as detailed in [Effective Go] and [Go Code Review Comments].
## General
### Ordering of declarations in a file
How declarations are organized within a Go file varies considerably, especially
since it is frequent to have one large file with all-the-things, or many smaller
files with each a-thing. We provide a few rules of thumb here.
For ordering of top-level declarations (constants, interfaces, types along with
their associated methods), it is common to list by group of functionality, with
the following order in each group:
* Constants (which includes enumerations);
* Exported interfaces, types, and functions;
* Unexported interfaces, types, and functions in support of the exported types.
As an example, a group of functionality in fidlgen lib may be the handling of
names, while another group may be reading and denormalizing the JSON IR. Such
groupings are good candidates to become their own separate files as they grow.
For a type with methods, it is common to list in order:
* The type declaration (e.g. `type myImpl struct { ...`);
* The [type assertion(s)](#type-assertions), if any;
* Methods, with exported methods first, and unexported methods second;
* It is best to group methods implementing an interface if applicable, i.e. all
methods for interface `Foo`, then all methods for interface `Bar`;
* Often, despite being exported, the `String()` method will be last.
For an enumeration, see how to [define an enum](#enum).
### Naming convention
Go is quite opinionated about how to name things. Some conventions:
* `var err error`
* `var buf bytes.Buffer`
* `var buf strings.Builder`
* `var mu sync.Mutex`
* `var wg sync.WaitGroup`
* `var ctx context.Context`
* `_, ok := someMap[someKey]`
* `_, ok := someValue.(someType)`
When defining methods, ensure that the name of the receiver is consistent (when
present). See also [pointers vs values](#pointers-vs-values).
### Avoid exporting
In Go, identifiers can be unexported or exported (the public/private terminology
is not used). An exported declaration starts with a capital letter
`MyImportantThing`, while an unexported declaration starts with a lowercase
letter `myLocalThing`.
Only export things you really need, and that you intend to reuse. Keep in mind
that exporting in a follow up change is relatively easy so prefer to not export
until necessary. This keeps the documentation cleaner.
If your type will be used by reflection, e.g. within templates or automatic
diffing à la [go-cmp], you can have an unexported identifier with exported
fields. Again, prefer unexported fields except in the situation where you are
forced to do otherwise.
There is nothing wrong with a local variable having the same name as a type. If
you would write `server := Server{...}` for an exported type, then `server :=
server{...}` is fine for an unexported type.
### Never use named return values
Semantics are confusing, and error prone. Even if it might be a good usage at
some point (rare, and doubtful), evolutions typically cause this not to be the
case anymore, which then introduces a diff that is larger than the actual
change.
You'll see advice saying that "it's fine to use named return values for this
special case", or "that special case is perfect for named return values". Our
rule is simpler, never use named return values.
(When defining interfaces, it is fine to name things. This is different from
named returns in implementations, it has no semantic implications.)
### Defining an enum {#enum}
The standard pattern to define an enumeration is:
```go
type myEnumType int
const (
_ myEnumType = iota
myFirstEnumValue
mySecondEnumValue
)
```
The type can only be elided when using `iota`. With an explicit value, you must
repeat the type:
```go
const (
_ myEnumType = iota
myFirstEnumValue
mySecondEnumValue
// ...
myCombinedValue myEnumType = 100
)
```
Additionally, if you want this enum to have a textual representation:
```go
var myEnumTypeStrings = map[myEnumType]string{
myFirstEnumValue: "myFirstEnumValue",
mySecondEnumValue: "mySecondEnumValue",
}
func (val myEnumType) String() string {
if fmt, ok := myEnumTypeStrings[val]; ok {
return fmt
}
return fmt.Sprintf("myEnumType(%d)", val)
}
```
For instance [mdlint:
TokenKind](https://cs.opensource.google/search?q=%22type%20TokenKind%20int%22&ss=fuchsia%2Ffuchsia).
Using a `map` is preferred over using a `switch` since it is very common to
evolve the code to compute things off of the map, e.g. a `FromString`
constructor (inefficiently by searching, or by pre-computing a `stringsToMyEnum`
reverse map).
You should only use an enum member with value `0` if it is a natural default.
For more details, see the [enum FIDL API Rubric entry][enum-fidl-rubric].
Another option is to use [`cmd/stringer`] to generate enumerations. This generates
more efficient code, but requires a little more effort to maintain. You can use
this approach when the enumeration has stabilized.
Note: Switching to `stringer` as the default recommended approached is tracked
as [fxbug.dev/73539](https://fxbug.dev/73539).
### Sets
To represent a set, use map to an empty struct:
```go
allMembers := map[uint32]struct{}{
5: {},
8: {},
}
```
Note: It is common to see sets defined as maps to `bool` (it is what [Effective
Go] recommends). In Fuchsia, we have chosen to always use an empty struct due to
the small performance improvement (which comes at the cost of small added
syntax).
### Instantiating slices
If you're going to define the slice using a literal, then `[]T{ ... }` is the
way to go.
Otherwise use `var slice []T` to create an empty slice, i.e. do not use
`slice:= []T{}`.
Note that pre-allocating slices can bring meaningful performance improvements,
and may be preferred over a simpler syntax. See for instance [slices usage and
internals](https://go.dev/blog/slices-intro).
### Instantiating maps
If you're going to define the map using a literal, then `map[K]T{ ... }` is the
way to go.
Otherwise use `make(map[string]struct{})` to create an empty map, i.e. do not
use `myEmptyMap := map[K]T{}`.
### Non-normal returns
When an function needs to communicate a non-normal return, i.e. a 'failure', there are a few patterns:
1. `func doSomething(...) (data, bool)`, i.e. either return the data, or false.
2. `func doSomething(...) *data`, i.e. either return the data, or nil.
3. `func doSomething(...) (data, error)`, i.e. either return the data, or an
error.
4. `func doSomething(...) dataWrapper`, i.e. return a wrapper which carries
structured information about the result of the operation.
Giving hard and fast rules for when to use which-flavor-where is not possible,
but there are some general principles that apply.
Relying on `nil` vs present is error prone in Go when used in polymorphic
contexts: `nil` is not a unit, there are [many `nil` and they do not equal each
other](https://play.golang.org/p/RvxsIbQ3NLH)! Therefore, prefer returning an
extra `ok` boolean when uses of the method will likely be in polymorphic
contexts. Only use `nil` vs present as an indication when the method will be
used solely in monomorphic contexts. Or said another way, using pattern (2) is
simpler than (1) and a good replacement when all callers will be using the
function without looking at the returned value through the lens of an interface.
Returning an `error` indicates that something problematic occurred, the caller
is provided with an explanation for what happened. It is expected for the caller
to bubble up the error, possibly wrapping it along the way, or do some sort of
error handling and recovery. A key distinction from a method returning an `ok`
boolean (or `nil` data), is that when returning an `error` the method is
asserting that it knows enough of the context to classify what is happening as
erroneous.
For instance, a `LookupUser(user_id uint64)` would favor returning an ok boolean
if no user was found, but a `LookupCountryCode(code IsoCountryCode)` would favor
returning an `error` to identify that something is misconfigured if a country
cannot be lookup up (or an invalid country was requested).
Returning some sort of wrapper should be considered in cases where the result of
a method is complex, and requires structured data to describe. For instance, a
natural candidate to use a data wrapper is a validation API which traverses an
XML document, and returns a list of faulty paths, each with warnings or errors
attached to them.
### Static type assertions {#type-assertions}
Since Go uses structural subtyping, i.e. whether a type implements an interface
is determined by its structure (not by a declaration). It is easy to think a
type implements some interface, when in fact it does not. This results in
breakage at a distance, at the use site, and makes for confusing compiler
errors.
To remedy this, it is required to write type assertions for all interfaces a
type implements (yes, those in the standard library
[too](https://cs.opensource.google/fuchsia/fuchsia/+/main:tools/fidl/lib/fidlgen/formatter.go;drc=e67febe6dbde880bf9632fbe0540cf9251dcfd0a;l=55)):
```go
var _ MyInterface = (*myImplementation)(nil)
```
This creates a typed nil, whose assignment forces the compiler to check for type
conformance.
Often, we have many implementations which have to implement the same interface,
e.g. representing an abstract syntax tree with individual nodes all implementing
the Expression interface. In these cases, you should do all type assertions at
once thus also documenting all subtypes which are expected:
```go
var _ = []MyInterface{
(*myImplementation)(nil),
(*myOtherImplementation)(nil),
...
}
```
A rule of thumb about where these type assertions should be placed is as
follows:
* Prefer having a single type assertion below the implementation when each
implementation stands on its own, e.g.
[here](https://cs.opensource.google/fuchsia/fuchsia/+/main:tools/mdlint/rules/bad_lists.go;drc=b9496c021b59ccfed5eeeaeacee5fb9dc14005a7;l=26);
* Prefer a grouped type assertion below the interface when all implementations
are meant to be used in concert (e.g. expression nodes of an `Expression`
interface representing an AST), e.g.
[here](https://cs.opensource.google/fuchsia/fuchsia/+/main:tools/fidl/lib/summarize/summary.go;drc=41966aa0f0d465f663b1b9928e5c57cf80fe38ef;l=35).
### Embedding
Embedding is a very powerful concept in Go. Make use of it. Read
[Embedding][Effective Go embedding] for an introduction.
When embedding an interface or a struct type, these should be listed first in
their enclosing interface or struct type, i.e. the embedded type(s) should
appear as the first field(s).
### Pointers vs Values {#pointers-vs-values}
For method receivers, read [Pointers vs. Values][Effective Go pointers_vs_values]
and [Receiver Type][Go Code Review Comments receiver-type].
tl;dr is to keep things consistent, but when in doubt, use a pointer receiver.
For a given type, always be consistent about how it is passed around, i.e.
always pass by value, always pass by reference, and this flows from whether
methods are defined on this type (with value or pointer receiver).
It is also worth noting that passing a struct by value, thinking that the caller
will not mutate it, is incorrect. You can easily hold maps, slices, or
references to objects, and therefore mutate those. So in Go it's an incorrect
association to think "pass by value is const".
Refer to [implementing interfaces](#implementing-interfaces) for specific advice
about method receivers.
### Implementing interfaces {#implementing-interfaces}
Generally implement interfaces using a pointer receiver; implementing an interface
using a value receiver causes that interface to be implemented by both value and
pointer, which complicates type assertions that attempt to enumerate possible
implementations of an interface. See for instance
[fxrev.dev/269371](https://fxrev.dev/269371).
There are some specific cases where implementing an interface using a value
receiver is appropriate:
* When the type is never used as a pointer. For example, custom sorting is often
done by defining `type mySlice []myElement` and implementing
[`sort.Interface`](https://golang.org/pkg/sort/#Interface) on `mySlice`. The
type `*mySlice` would never be used because `[]myElement` is already a
reference. Example
[here](https://cs.opensource.google/fuchsia/fuchsia/+/main:tools/mdlint/core/reporter.go?q=%22type%20sortableMessages%20%5B%5Dmessage%22).
* When it is never expected to use a type assertion or type switch on values of
the interface type. For example,
[`Stringer`](https://golang.org/pkg/fmt/#Stringer) is often implemented on
value types. It would be unusual for a function accepting `val Stringer` to
switch on `val.(type)`.
When in doubt, always implement interfaces using a pointer receiver.
### Comments
Read [Commentary][Effective Go commentary], in particular:
> Doc comments work best as complete sentences, which allow a wide variety of
> automated presentations. The first sentence should be a one-sentence summary
> that starts with the name being declared.
```go
// Compile parses a regular expression and returns, if successful,
// a Regexp that can be used to match against text.
func Compile(str string) (*Regexp, error) {
```
Doc comments on types [may have a leading
article](https://github.com/golang/lint/blob/738671d3881b9731cc63024d5d88cf28db875626/lint.go#L832)
"A", "An", or "The". All documentation in the Go standard library follows this
practice, [e.g.](https://golang.org/pkg/bytes/#Buffer) `// A Buffer is a
variable-sized ...`.
For comments which are more than a sentence long, the style generally prefers
one summary sentence, then an empty line, then a paragraph providing further
details. See for instance the
[FileHeader](https://golang.org/pkg/archive/zip/#FileHeader) struct.
### Error wrapping {#error-wrapping}
When propagating errors using `fmt.Errorf`:
* Use `%s` to only include their string values;
* Use `%w` to allow callers to unwrap and observe wrapped errors; note `%w`
makes those wrapped errors part of your API.
See [Working with Errors in Go
1.13](https://blog.golang.org/go1.13-errors), and more specifically [Whether
to Wrap](https://blog.golang.org/go1.13-errors#TOC_3.4.).
There are some specific cases where error propagation must be done in a way that
satisfies an API contract, e.g. often the case in a RDBMS driver where specific
error codes returned indicate situations callers may recover from. In such
cases, it is necessary to specifically wrap underlying errors rather than rely
on `fmt.Errorf`.
### `fmt` verbs
Avoid `%v` when possible, preferring specific fmt verbs that are supported by
the operand. This has the benefit of allowing `go vet` to check that the verb is
indeed supported by the operand.
In cases where the operand is a struct that doesn't implement `fmt.Stringer`,
`%v` is unlikely to produce a good result anyhow; `%+v` or `%#v` are likely much
better choices.
A common exception to this rule is an operand that may be `nil` at run-time -
`nil` values are well handled by `%v` but not all other verbs.
When quoting strings, use `%q` instead of explicitly calling `strconv.Quote`.
When propagating errors, see [error wrapping](#error-wrapping).
### GN targets
A typical `BUILD.gn` file for a Go tool will look something like this:
```gn
go_library("gopkg") {
sources = [
"main.go",
"main_test.go",
]
}
go_binary("foo") {
library = ":gopkg"
}
go_test("foo_test") {
library = ":gopkg"
}
```
If you have nested packages (and [only in this
case](https://fuchsia-review.googlesource.com/c/fuchsia/+/406682/)), use
`name = "go.fuchsia.dev/fuchsia/<path>/..."` form in go_library to enable
recursive package sources:
```gn
go_library("gopkg") {
name = "go.fuchsia.dev/fuchsia/tools/foo/..."
sources = [
"main.go",
"subdir/bar.go",
"extra/baz.go",
]
}
```
Note: We are moving away from allowing `...` in `go_library` altogether,
reserving this form for third-party code which we want to purposefully view as a
black box. However, it is frequent for code to be decomposed into multiple
packages, without individual packages being deemed "library quality", i.e. in a
shape to be reused at will. We plan to update the various GN templates to
reflect these subtleties, and offer a `go_private_library` template allowed to
be used solely by a single `go_binary` rule, and `go_third_party_library` which
would be the only allowing the use `...`. This is tracked as
[fxbug.dev/73815](https://fxbug.dev/73815).
## Testing
### Packages ending in _test
Usually a `_test.go` file is in the same package as the code it tests (e.g.
`package foo`), and it can access unexported declarations. [Go also allows
you](https://tip.golang.org/cmd/go/#hdr-Test_packages) to suffix the package
name with `_test` (e.g. `package foo_test`), in which case it is compiled as a
separate package, but linked and run with the main binary. This approach is
called external testing, as opposed to internal testing. Do not name a package
with the `_test` suffix unless you are writing external tests, see instead
[testing package](#testing-packages).
Prefer external tests when doing integration level testing, or to interact with
the exported only portion of a package under test.
Using packages ending in `_test` is also interesting to provide compiled example
code, which is copy pastable as-is with the package selector. For instance
[example
code](https://pkg.go.dev/github.com/maruel/panicparse/v2/stack#example-package-Simple)
and [its
source](https://github.com/maruel/panicparse/blob/master/stack/example_test.go#L161).
### Test utilities
Testing utilities are helpers used within a package, to help with testing. It is
'testing code' in that it lives in a file with a `_test.go` suffix. Place
testing utilities in a file named `testutils_test.go` file; this convention is
interpreted by the compiler such that this code is not included in non-test
binaries, which ensures it isn't used outside of tests.
### Testing packages {#testing-packages}
A testing package is a library whose focus is to make writing tests easier. It
is 'production code' — does not end in `_test.go` suffix — but intended only to
be used from test code.
The naming convention for testing packages is to use a "test" suffix with the
package name it is used to test. Examples in the standard library are
[httptest](https://golang.org/pkg/net/http/httptest/) and
[iotest](https://golang.org/pkg/testing/iotest/), in the Fuchsia tree
[fidlgentest](/tools/fidl/lib/fidlgentest/), and
[emulatortest](/tools/emulator/emulatortest).
## Additional resources
* [Effective Go]
* [Go Code Review Comments], view this as a supplement to [Effective Go]
* [Go FAQ](https://golang.org/doc/faq)
* [golint](https://pkg.go.dev/golang.org/x/lint/golint)
* [Go at Google: Language Design in the Service of Software
Engineering](https://talks.golang.org/2012/splash.article)
<!-- link labels -->
[`cmd/stringer`]: https://pkg.go.dev/golang.org/x/tools/cmd/stringer
[Effective Go commentary]: https://golang.org/doc/effective_go#commentary
[Effective Go embedding]: https://golang.org/doc/effective_go#embedding
[Effective Go pointers_vs_values]: https://golang.org/doc/effective_go#pointers_vs_values
[Effective Go]: https://golang.org/doc/effective_go
[enum-fidl-rubric]: /docs/development/api/fidl.md#enum
[Go Code Review Comments receiver-type]: https://github.com/golang/go/wiki/CodeReviewComments#receiver-type
[Go Code Review Comments]: https://github.com/golang/go/wiki/CodeReviewComments
[go-cmp]: https://github.com/google/go-cmp