agentsclimarketplace

Go interfaces

Skill muratmirgun/gophers/skills/go-interfaces

26 production-grade Go skills for Claude Code, Gemini CLI, and opencode.

Install
npx -y skills add muratmirgun/gophers --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

  • 8 stars8 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

Use when defining or implementing Go interfaces, composing types through embedding, designing dependency-injection seams, or deciding between pointer and value receivers. Apply proactively whenever a new abstraction is introduced or a constructor returns an abstract type, even if the user has not asked about interfaces. Does not cover generics (see go-generics).

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

8.9 KB, as published. Nobody here has run it

Go Interfaces and Composition

Interfaces in Go are consumer contracts, not implementation hierarchies. They should be small, discovered late, and owned by the package that uses them — not the package that satisfies them.

Core Rules

  1. Accept interfaces, return concrete types. Consumers state what they need; producers expose what they have.
  2. Interfaces belong in the consumer package. Defining an interface next to its sole implementation is almost always wrong.
  3. Don't design with interfaces — discover them. Wait for a second implementation or a test mock to demand one.
  4. The bigger the interface, the weaker the abstraction. Aim for 1–3 methods; compose larger contracts from smaller ones.
  5. Receiver consistency: if any method needs a pointer receiver, give every method a pointer receiver.
  6. Verify satisfaction at compile time with var _ I = (*T)(nil) when the relationship must not break silently.
  7. Use the comma-ok idiom for every type assertion. A bare assertion panics on mismatch.

Decision: Should I Introduce an Interface?

SituationVerdict
Single implementation, no tests need to swap itNo interface. Use the concrete type.
Second implementation appears (or is imminent)Extract an interface in the consumer package.
Test needs to fake an external dependencyDefine a small interface in the consumer; pass a fake.
You want to expose optional behaviour (Flusher, ReaderFrom)Define a tiny interface; check with _, ok := v.(Iface).
You want a stable plugin/SPI boundaryYes, but keep it minimal and version it explicitly.

Read references/consumer-owned-interfaces.md when migrating a producer-defined interface back to the consumer, or when designing a new package boundary.

Accept Interfaces, Return Concrete Types

// Good — consumer defines what it needs
package notify
type Sender interface { Send(to, body string) error }
type Service struct{ s Sender }
func NewService(s Sender) *Service { return &Service{s: s} }

// Good — producer returns a concrete type
package email
type Client struct{ /* ... */ }
func New(cfg Config) *Client { /* ... */ }
func (c *Client) Send(to, body string) error { /* ... */ }
// Bad — producer defines and returns its own interface,
// forcing every consumer to depend on email.Sender.
func New(cfg Config) Sender { return &client{...} }

The exception is "expose an interface, hide the implementation": when a type has no exported methods beyond what the interface promises, returning the interface (func NewHash() hash.Hash32) is fine.

Keep Interfaces Small

Standard library interfaces are the model: io.Reader, io.Writer, io.Closer, fmt.Stringer, error — one or two methods each. Compose larger contracts:

type ReadWriteCloser interface { io.Reader; io.Writer; io.Closer }

If you find yourself writing a five-method interface, split it until each piece has a single reason to exist.

Compile-Time Satisfaction Check

var _ io.ReadWriter = (*MyBuffer)(nil)

Use when the type must satisfy an interface for correctness (custom JSON marshalling, http.Handler) and no other static use already enforces it. Don't add one for every interface.

Type Assertions and Type Switches

Always use the comma-ok form. Type switches re-declare the variable; cases with multiple types fall back to the interface type. Use optional-behaviour assertions to enhance a path without requiring the capability:

s, ok := v.(string)                              // comma-ok
switch x := v.(type) { case string: /* ... */ }  // type switch
if f, ok := w.(http.Flusher); ok { f.Flush() }   // optional behaviour

Embedding: Composition, Not Inheritance

Struct embedding promotes the inner type's methods and fields to the outer type. Use it deliberately — every promoted method becomes part of your public API.

type Server struct {
    *slog.Logger          // exposes Info/Warn/Error on Server
    addr string
}
Use embedding whenUse a named field when
You want the outer type to be an enhanced version of the innerYou only need the inner type internally
The full inner API should be promotedYou want to delegate explicitly to a subset

Avoid embedding in exported types unless the promotion is the whole point. The inner type's method set is locked in once published.

Read references/embedding-and-receivers.md when designing struct embedding, overriding promoted methods, resolving name conflicts, or choosing between pointer and value receivers.

Dependency Injection via Interfaces

Constructors take interfaces; tests pass fakes. No DI container required.

type UserStore interface {
    FindByID(ctx context.Context, id string) (*User, error)
}

type UserService struct{ store UserStore }
func NewUserService(s UserStore) *UserService { return &UserService{store: s} }

The UserStore interface lives in the package that defines UserService. The concrete *pgUserStore lives in a database package and doesn't know UserService exists.

Preventing Accidental Copies

Structs that must not be copied (those holding a mutex, internal pointers, or a sync.WaitGroup) should embed a noCopy sentinel so go vet catches the mistake:

type noCopy struct{}
func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

type ConnPool struct {
    _   noCopy
    mu  sync.Mutex
    /* ... */
}

Pass these by pointer (func process(p *ConnPool)), never by value.

Don't reach for noCopy reflexively. Plain value types (config structs, request DTOs, immutable snapshots) should be copyable — adding noCopy to them locks consumers into pointer-only APIs for no gain. The rule of thumb: if the struct owns a sync.Mutex, sync.WaitGroup, sync.Pool, internal chan, or a pointer that must stay unique (file handle, OS resource), embed noCopy. Otherwise leave it copyable.

Anti-Patterns

Anti-patternWhy it hurtsDo this instead
Producer-defined interface returned from constructorCouples every consumer to the producer's packageReturn the concrete type; let consumers define interfaces
Five-plus method interfaceHard to implement, hard to mockSplit into small interfaces; compose
Premature interface with one implementationIndirection without valueStart concrete; extract when a second consumer appears
v := x.(T) without okPanics on mismatchv, ok := x.(T)
Embedding a concrete type into an exported structInner API leaks into your public surfaceUse a named, unexported field
Mixing pointer and value receivers on one type(*T) and T have different method sets — confusing satisfaction errorsPick one receiver style for the whole type
ToString() / ReadData() instead of canonical namesBreaks fmt.Stringer / io.Reader discoveryHonour String() / Read(p []byte) (int, error)
Returning *MyErr instead of errorTyped-nil trap; err != nil is true even when "no error"Return the interface type

Verification Checklist

Before finishing an interface change:

  • Interfaces are defined in the package that consumes them
  • Constructors return concrete types (or hide a single unexported implementation behind a small interface)
  • No interface has more than ~3 methods unless it composes named smaller ones
  • Every type assertion uses the comma-ok form
  • Pointer vs value receivers are consistent across all methods on a type
  • Compile-time var _ I = (*T)(nil) exists where silent regressions would hurt
  • Exported structs don't accidentally promote inner-type APIs through embedding

References

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.