Read any Go function header at a glance. It's the densest line in the file — and the one that tells you the most.
*,
[], receivers, multi-return) into one line, and it's the first thing you read in every PR hunk.
From services/asset/service.go — a real signature you'll review.
name type, name first
returns — value + error, always last
Read left-to-right, five beats:
func — it's a function.(s *Service) — the receiver. This is a method on *Service. s is the
Go equivalent of Python's self — but you name it yourself, and it sits before the function name.GetAssetByImplementation — the name. Capital G ⇒ exported (public). lowercase ⇒ package-private.(ctx context.Context, …) — parameters. Each is name type — name first, type after
(the reverse of C/Java). ctx context.Context being first is a hard convention. [rule](*models.Asset, error) — the return. Two values: a pointer to an Asset, and an error. The
error-last shape from Lesson 1.These are the symbols that don't exist (or mean something else) in Python. Memorize this table; it's the bulk of the friction.
| Go token | Reads as | Python instinct to override |
|---|---|---|
name type | variable/param name of type — type comes after | No annotations / name: type |
*T | "pointer to T" — an address, may be nil | Everything's a reference; no * |
&x | "address of x" — take a pointer to it | No address-of operator |
[]T | "slice of T" — a growable list | list[T] |
map[K]V | "map from K to V" | dict[K, V] |
x := v | declare and assign, type inferred (new variable) | x = v for everything |
x = v | assign to an already-declared x | same = as above — Go splits them |
(A, B) return | multiple return values (a tuple-ish, but not a tuple type) | return a, b packs a real tuple |
chan T / <-ch | "channel of T" / "receive from ch" (concurrency — later lesson) | No direct analog (asyncio queues, loosely) |
`tag:"..."` | struct tag — metadata read by libs via reflection | Decorators / pydantic Field(...) |
:= vs =. := declares a new variable;
= assigns to an existing one. A PR that writes x := inside an inner scope when it meant to
update the outer x creates a shadowed variable — a fresh local that silently masks the outer one.
This is a real, common Go bug to flag. (We'll hunt shadowing directly in a later lesson.)
* in two roles — don't conflate themThe asterisk does two different jobs depending on where it sits. Reviewers must read them apart instantly:
// in a TYPE position → "pointer to"
func f(a *Asset) // a is *Asset
var p *Asset // p holds an address
(s *Service) // pointer receiver
// in an EXPRESSION → "value at" (deref)
x := *p // read the Asset p points to
*p = newAsset // write through the pointer
// and & is the inverse:
p = &asset // take address of asset
Rule of thumb when scanning: * next to a type name = "pointer to"; * next to a
variable in an expression = "the value it points at". & is always "give me the address."
The deep why (when Go copies, why pointer receivers exist) is Lesson 4 — here you just need to read them.
// struct with tags (config, seen all over the repo)
type Config struct {
UpdatePeriod time.Duration `validate:"required"` // field type `tag`
BatchSize int `validate:"required"`
}
// constructor returning the (value, cleanup, error) triple — a repo idiom
func NewService(config *Config, logger *zap.Logger, client Client) (*Service, func(), error)
// params: name type, name type, name type returns: *Service, a func(), error
// short var decl with inferred type + comma-ok (Lesson 1)
dApp, ok := dApps[id] // dApp:*DApp, ok:bool — both NEW vars via :=
Note func() as a type in the return list — Go functions are values, so "a function taking nothing,
returning nothing" is a first-class type. That's the cleanup closure you saw in Lessons 1–2.
Read each header. Instant feedback. Reps, not grades.
func (c *Client) ListDApps(ctx context.Context, limit, offset int64) ([]*dapp_storage.DApp, error)What does this return?asset, err := s.getAsset(ctx, chain, addr, cur, false)What is := doing here?func (s *Service) GetAssetByID(ctx context.Context, chain types.Chain, id string, currency types.Currency) (*models.Asset, error)Pick the receiver — the "self".p := &asset
*p = updatedWhat do these two lines do?reference/. Or paste any line from a real PR that won't parse for you and I'll decode it token-by-token.
Added to your GLOSSARY.md:
(s *Service) before a method name; Go's explicit, self-named self.:=) — declare + assign + infer type, for a new variable.*T / &x / *p) — type "pointer to T", "address of x", "value at p".[]T) — a growable, reference-backed list of T.`key:"val"` metadata after a field, read by libraries via reflection.