agentsclimarketplace

Go interfaces

Skill ctoth/golang-skills-plugin/plugins/golang/skills/go-interfaces

Agent skill plugins for Go code quality and performance work.

Install
npx -y skills add ctoth/golang-skills-plugin --skill go-interfaces

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Guides Go interface design and the typed-nil-error gotcha — accept interfaces and return concrete structs, keep interfaces small (1–3 methods, `-er` names), define them on the consumer side not the producer, avoid `any`/`interface{}` as a parameter type, compose with embedding, and never return a concrete `*MyError`/pointer type where the value can be nil. Auto-invokes when writing or editing interface definitions, function signatures that take or return interfaces, `any`/`interface{}` parameters, interface embedding, or functions returning concrete error/pointer types — and on "why is this nil check failing", "is this always non-nil", or "should this be an interface". An interface holds a (type, value) pair, so a nil pointer inside one is not a nil interface.

SKILL.md

15.2 KB, as published. Nobody here has run it

Go Interfaces

"The bigger the interface, the weaker the abstraction." · "interface{} says nothing." — Go Proverbs

"Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here." — Effective Go

"Go interfaces generally belong in the package that uses values of the interface type, not the package that implements those values." — Go Code Review Comments

A Go interface is a set of method signatures, satisfied structurally — a type implements it just by having the methods, with no implements keyword. That makes interfaces cheap to add and easy to over-add. The discipline below is about adding them where they earn their place, keeping them small, and understanding the one representation fact (an interface is a (type, value) pair) that turns a returned nil pointer into a non-nil error.


1. The Headline Rule: Accept Interfaces, Return Structs

Take the narrowest interface you need as input; return the concrete type as output. The Google Style Guide states it directly: "Functions should take interfaces as arguments but return concrete types" (Google Go Style Guide — Decisions). The reason is asymmetric: accepting an interface lets every caller — including a test fake — pass whatever satisfies it, while returning a concrete type means "new methods can be added to implementations without requiring extensive refactoring" (CodeReviewComments).

// WRONG — returns an interface, hiding the concrete type and freezing the method set
func NewStore() StoreInterface { return &store{} }

// RIGHT — accept the small interface you use; return the concrete *Store
func NewStore() *Store { return &Store{data: map[string]string{}} }

func describe(g itemGetter, id string) (string, error) { // accepts an interface
	v, err := g.Get(id)
	if err != nil {
		return "", fmt.Errorf("describing %s: %w", id, err)
	}
	return "item=" + v, nil
}

2. The Rules and Their Sources

RuleThe disciplineSource
Accept interfaces, return structsNarrow interface in; concrete type out"Functions should take interfaces as arguments but return concrete types" (Google Decisions)
Keep interfaces small1–3 methods; -er names"one-method interfaces are named by the method name plus an -er suffix ... Reader, Writer, Formatter" (Effective Go); "The bigger the interface, the weaker the abstraction" (Proverbs)
Define on the consumer sideThe user declares it, not the implementor"Go interfaces generally belong in the package that uses values of the interface type" (CodeReviewComments)
Not before they're usedNo interface without a real second use"Do not define interfaces before they are used" (CodeReviewComments)
Not "for mocking"Test against the real API"Do not define interfaces on the implementor side of an API 'for mocking'" (CodeReviewComments)
any says nothingAvoid any/interface{} params"interface{} says nothing" (Proverbs)
Compose by embeddingBuild big interfaces from small onesio.ReadWriter = Reader + Writer (Effective Go)
Declare error returns as errorNever return concrete *MyError"use the error type in their signature ... rather than a concrete type such as *MyError" (Go FAQ)

3. Keep Interfaces Small — the -er Convention

Idiomatic Go interfaces are tiny. "Interfaces with only one or two methods are common in Go code, and are usually given a name derived from the method" (Effective Go); "one-method interfaces are named by the method name plus an -er suffix ... Reader, Writer, Formatter, CloseNotifier" (Effective Go). The canonical example is io.Reader — one method, used everywhere:

// io.Reader, the most-used interface in the standard library:
type Reader interface {
	Read(p []byte) (n int, err error)
}

A small interface is a strong abstraction because almost anything can satisfy it, and almost nothing breaks when an implementation changes. A large one — a "manager" interface mirroring a whole struct — is the opposite: "The bigger the interface, the weaker the abstraction" (Go Proverbs). Prefer the stdlib interfaces (io.Reader, io.Writer, fmt.Stringer, error) before inventing your own.


4. Define Interfaces on the Consumer Side

The package that uses a behavior declares the interface; the package that provides it returns a concrete type. "The consumer of the interface should define it (not the package implementing the interface), ensuring it includes only the methods they actually use" (Google Go Style Guide — Decisions).

The most common LLM anti-pattern here is the producer-side interface created "for mocking." Go rejects it twice: "Do not define interfaces on the implementor side of an API 'for mocking'; instead, design the API so that it can be tested using the public API of the real implementation"; and "Do not define interfaces before they are used: without a realistic example of usage, it is too difficult to see whether an interface is even necessary, let alone what methods it ought to contain" (CodeReviewComments). "Avoid creating interfaces until a real need exists" (Google Decisions).

// WRONG — producer package exports an interface mirroring its own struct
package producer

type Thinger interface{ Thing() bool }       // DO NOT DO IT
func NewThinger() Thinger { return defaultThinger{} }

// RIGHT — producer returns a concrete type; the CONSUMER declares the small interface it needs
package producer
type Thinger struct{ /* ... */ }
func (t Thinger) Thing() bool { /* ... */ }

package consumer
type thinger interface{ Thing() bool }       // only the method this package uses
func Foo(t thinger) string { /* ... */ }

Because satisfaction is structural, the consumer's interface needs no cooperation from the producer — a test fake in the consumer's own package satisfies it for free.


5. any / interface{} Says Nothing

The empty interface carries no behavior, so a parameter typed any tells the caller and the compiler nothing about what is allowed. "interface{} says nothing" (Go Proverbs). Reach for a concrete type, a small behavioral interface, or — when the logic is genuinely identical across types — a generic type parameter, not any.

// WRONG — `any` defeats the type system; every use needs a runtime type assertion
func Write(w io.Writer, v any) error { /* type-switch on v ... */ }

// RIGHT — name the behavior you require
func Write(w io.Writer, v fmt.Stringer) error {
	_, err := io.WriteString(w, v.String())
	return err
}

The interface-vs-type-parameter decision is owned by go-generics: if all you do is call a method, an interface is simpler than a type parameter.


6. Compose Interfaces by Embedding

Build larger interfaces by embedding smaller ones rather than re-listing methods. "it's easier and more evocative to embed the two interfaces to form the new one" (Effective Go):

// from the standard library:
type ReadWriter interface {
	Reader
	Writer
}

"A ReadWriter can do what a Reader does and what a Writer does; it is a union of the embedded interfaces" (Effective Go). Embedding keeps each piece small and lets a type satisfy the composite by satisfying the parts.


7. Satisfaction Is Structural — Verify It at Compile Time

A type satisfies an interface implicitly, just by having the methods. To guarantee a type still satisfies an interface (and to get a clear compile error the moment it stops), use the blank-identifier assignment. "To guarantee that the implementation is correct, a global declaration using the blank identifier can be used" — var _ json.Marshaler = (*RawMessage)(nil) — and "that property will be checked at compile time" (Effective Go).

// Compile-time proof that *Handler implements http.Handler:
var _ http.Handler = (*Handler)(nil)

"The statement var _ http.Handler = (*Handler)(nil) will fail to compile if *Handler ever stops matching the http.Handler interface" (Uber Go Style Guide). The right-hand side is the zero value of the asserted type: nil for pointers, slices, and maps; an empty struct literal for struct types. Use it for exported types whose interface contract is part of their API.


8. Pointer vs Value Receivers and the Method Set

Whether a type satisfies an interface depends on which receiver its methods use. "Methods with value receivers can be called on pointers as well as values. Methods with pointer receivers can only be called on pointers or addressable values" (Uber Go Style Guide). So a value-receiver method puts the method in both the value's and the pointer's method set, but a pointer-receiver method is in the pointer's method set only:

type F interface{ f() }

type S1 struct{}
func (s S1) f() {}      // value receiver

type S2 struct{}
func (s *S2) f() {}     // pointer receiver

var i F
i = S1{}    // ok — value receiver
i = &S1{}   // ok
i = &S2{}   // ok — pointer
// i = S2{} // DOES NOT COMPILE: S2 value has no f in its method set

This is why "accept interfaces, return structs" usually returns a *T: a single pointer value satisfies interfaces whether the methods use value or pointer receivers.


9. The Typed-Nil Gotcha (Why Your != nil Check Lies)

An interface value is a pair. "Under the covers, interfaces are implemented as two elements, a type T and a value V" (Go FAQ); "A variable of interface type stores a pair: the concrete value assigned to the variable, and that value's type descriptor" (The Laws of Reflection). And critically: "An interface value is nil only if the V and T are both unset" (Go FAQ).

So if you return a concrete *MyError that happens to be nil, the error interface that receives it holds (T=*MyError, V=nil) — a non-nil interface. "Such an interface value will therefore be non-nil even when the pointer value V inside is nil" (Go FAQ). The caller's if err != nil is then unexpectedly true on the success path.

// WRONG — returns the concrete *MyError; on success p is a nil *MyError,
// but the returned error interface is (T=*MyError, V=nil) — NON-nil.
func buggyValidate(bad bool) error {
	var p *MyError
	if bad {
		p = &MyError{Msg: "bad input"}
	}
	return p // caller's `err != nil` is ALWAYS true
}

// RIGHT — declare the return as error, and return an explicit nil on success.
func correctValidate(bad bool) error {
	if bad {
		return &MyError{Msg: "bad input"}
	}
	return nil
}

This is proven, not asserted: a test that calls buggyValidate(false) (nothing bad happened) finds err == nil is falseBUG reproduced: nothing bad happened, yet (err != nil) == true — while a type assertion confirms the dynamic value is a nil *MyError. The fixes, both from the FAQ: "the function must return an explicit nil," and "It's a good idea for functions that return errors always to use the error type in their signature ... rather than a concrete type such as *MyError" (Go FAQ). The standard library follows this: os.Open returns error, never the concrete *os.PathError. Declaring error returns (not concrete types) is owned by go-error-handling.


10. Who Suffers When Interfaces Are Done Badly

  • The teammate debugging at 2am who reads if err != nil { return err }, sees the error is "set," and burns an hour before learning the function returned a typed nil — the != nil was lying the whole time (Section 9).
  • The reviewer forced to navigate a StoreInterfacestoremockStore maze generated "for testing," when a concrete return and a three-line consumer interface would have done the job (Section 4). "The bigger the interface, the weaker the abstraction" (Proverbs) is the empathy rule: every method on a too-big interface is a method the next person must understand to mock or change.
  • The next caller of a function typed func(any), who gets no compiler help and must read the body to learn which types are actually allowed (Section 5).

11. Routing to the Specific Skills

  • go-idiomatic-discipline — the policy root. Producer-side interface pollution is its axis 2 (over-abstraction); this skill holds the depth.
  • go-generics — the interface-vs-type-parameter decision. "If all you need to do with a value of some type is call a method on that value, use an interface type, not a type parameter."
  • go-error-handling — declaring error (not *MyError) returns, wrapping with %w, and the typed-nil error in the context of error values.
  • go-zero-values-and-construction — "accept interfaces, return structs" on the construction side: returning a useful concrete zero value rather than an interface.

12. Reference Files

High-frequency interface anti-patterns in LLM-generated Go, each with wrong/right code and citations:

${CLAUDE_SKILL_DIR}/references/common-mistakes.md

Source provenance for every claim in this skill:

${CLAUDE_SKILL_DIR}/references/sources.yaml

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.