Skip to content

ADR 0022: Capitalization is visibility; Lyth names are Go names

Status: Accepted (supersedes 0010; amends 0013)

ADR 0010 kept Lyth’s own naming: declarations were private unless pub, struct fields public unless private, and the Go name was the Lyth name with its first letter changed to match. Go APIs were referenced verbatim, so every file already mixed the two conventions (r.URL.Query() next to store.list()). On top of that, the name a programmer wrote was not the name that existed: pub const defaultLimit was DefaultLimit in Go, and the field id was Id. Stack traces, pprof, delve, go doc, Go callers, grep, and code generated by tools all showed the Go spelling. The mechanical capitalization also produced non-idiomatic Go (Id, UserId) that Go linters flag, and the initialism heuristic that would fix it was rejected as a guess.

  • Visibility follows Go’s rule. A top-level declaration, method, struct field, or interface method whose name starts with an uppercase letter is exported; any other is unexported. There are no pub or private keywords; writing one is E0008 with a hint naming the declaration’s exported or unexported spelling.
  • The Go name of a Lyth declaration is its Lyth name. The one exception is an unexported name that is a Go keyword or predeclared identifier, which gets a trailing _ (fun string() is string_ in Go).
  • Exported struct fields get an automatic json key in lower camel case: the leading uppercase run is lowered, keeping the last letter of an initialism that starts the next word (Name → name, ID → id, UserID → userID, HTTPPort → httpPort). An explicit json key is used verbatim, as before. Unexported fields are not serialized, as in Go.
  • Diagnostics speak of exported and unexported names, and suggest the renamed spelling (rename it to Find to export it).
  • Keep pub with mechanical capitalization (ADR 0010): keeps camelCase Lyth APIs but makes source and binary names differ everywhere a Go tool shows a name, and still leaves two casing styles in every file.
  • pub with an initialism heuristic (userId → UserID): a guess that fails on names like IPv4, and one more mapping to learn.
  • Lower-case aliases for Go APIs: rejected in ADR 0010 for the same reasons as before (collisions, two spellings, copy-paste from Go docs).
  • Private fields by default with explicit export: brings back Go’s JSON trap differently; capitalization at least makes the rule visible at the field.

Lyth code reads as Go reads: exported functions, types, fields, and methods are capitalized (fun NewServer, todo.Todo, t.ID, fun T.String()), and one casing convention covers Go and Lyth APIs alike. Generated Go is unchanged for existing programs, which were migrated by renaming every declaration to its former Go name. Implementing a Go interface needs no keyword: a method named String is exported. Lower-case fields are invisible to encoding/json and other reflection-based libraries, Go’s beginner trap; a warning for passing such a struct to json.Marshal is a possible follow-up. The open question about initialisms is resolved: programmers write ID themselves.