go-interfaces
Skill7 starsChanged 9 days agoApache-2.0View on GitHub
Use when designing or implementing Go interfaces, abstractions, mockable boundaries, embedding, assertions, type switches, pointer versus value receiver types, or interface versus concrete API types. Generics belong to go-generics.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- CasLubbers/code-design-skillstheir own version, first seen Aug 25, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- CasLubbers/code-design-skills★ 2same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository25 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- go-code-refactorSkill
- go-code-reviewSkill
- go-codeSkill
- go-concurrencySkill
- go-contextSkill
- go-databaseSkill
- go-data-structuresSkill
- go-defensiveSkill
- go-documentationSkill
- go-error-handlingSkill
- go-functionsSkill
- go-genericsSkill
- go-httpSkill
- go-lintingSkill
- go-loggingSkill
- go-namingSkill
- go-packagesSkill
- go-performanceSkill
- go-resilienceSkill
- go-securitySkill
- go-style-coreSkill
- go-testingSkill
- go-troubleshootingSkill
The file
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 5 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 104
Avoid embedding types in public structs — the inner type's full method set becomes part of your public API, so adding, removing, or …
- Distinctive: line 114
Use a blank identifier assignment to verify a type implements an interface at compile time:
- Distinctive: line 123
Use this pattern when:
- Distinctive: line 129
Don't: add these checks for every interface — only when no other static conversion would catch the error.
- Distinctive: line 132
Before changing an interface's method set, list its implementations: the build reports each one assigned or passed as the interface, but a …
- Very specific: line 153
Default to a pointer receiver.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Go Interfaces and Composition
Compatibility: Baseline Go 1.27 (see COMPATIBILITY.md). Generic methods require Go 1.27+.
Resource Routing
scripts/check-interface-compliance.sh- Run as a heuristic; Interface Satisfaction Checks says what it lists.scripts/check-interface-compliance.go- Implementation helper built and run bycheck-interface-compliance.sh; not run or read directly.references/EMBEDDING.md- Read when embedding a type in a public struct.references/RECEIVER-TYPE.md- Read when choosing pointer or value receivers.
Accept Interfaces, Return Concrete Types
Interfaces belong in the package that consumes values, not the package that implements them. Return concrete (usually pointer or struct) types from constructors so new methods can be added without refactoring.
// Good: consumer defines the interface it needs
package consumer
type Thinger interface { Thing() bool }
func Foo(t Thinger) string { ... }// Good: producer returns concrete type
package producer
type Thinger struct{ ... }
func (t Thinger) Thing() bool { ... }
func NewThinger() Thinger { return Thinger{ ... } }// Bad: producer defines and returns its own interface
package producer
type Thinger interface { Thing() bool }
type defaultThinger struct{ ... }
func NewThinger() Thinger { return defaultThinger{ ... } }Do not define interfaces before they are used. Identify the consumer and the substitution it needs, including a test double. One production implementation neither requires nor rules out an interface.
Generality: Hide Implementation, Expose Interface
Return an interface from a constructor only when it is a pre-existing one owned by the consumer or the standard library, and the type has no exported methods beyond it:
func NewHash() hash.Hash32 {
return &myHash{} // unexported type; hash.Hash32 is the stdlib's interface
}An interface declared beside the constructor in order to be returned is the Bad case above, whatever the type's method set.
Type Assertions: Comma-Ok Idiom
Without checking, a failed assertion panics. Use the comma-ok idiom whenever
the dynamic type is data-dependent; a bare x.(T) is acceptable only where a
mismatch is a programming error that should panic — a value the package
itself stored under a key only it writes. A recover guard is not one: a bare
assertion replaces a foreign panic value with a *runtime.TypeAssertionError,
so it uses comma-ok and re-panics p unchanged
(PANIC-RECOVER.md).
Reflection code uses reflect.TypeAssert[T] (Go 1.25+) instead of
v.Interface().(T).
Generic Methods Cannot Satisfy Interfaces
Go 1.27 lets a method declare its own type parameters, but interface methods
still cannot. A type whose only implementation of Do is Do[T any]() does
not satisfy interface{ Do() } — the compile-time assertion
var _ I = (*T)(nil) is how you find out. When callers dispatch through an
interface, keep a non-generic method (or a free generic function) as the entry
point. See go-generics.
Embedding
Avoid embedding types in public structs — the inner type's full method set becomes part of your public API, so adding, removing, or replacing the embedded type is a breaking change. Use an unexported field and forward the methods you mean to export; EMBEDDING.md has the before/after. This skill owns the rule; go-defensive routes here.
Interface Satisfaction Checks
Use a blank identifier assignment to verify a type implements an interface at compile time:
var _ json.Marshaler = (*RawMessage)(nil)This causes a compile error if *RawMessage doesn't implement json.Marshaler.
Use this pattern when:
- There are no static conversions that would verify the interface automatically
- The type must satisfy an interface for correct behavior (e.g., custom JSON marshaling)
- Interface changes should break compilation, not silently degrade
Don't add these checks for every interface — only when no other static conversion would catch the error.
Before changing an interface's method set, list its implementations: the build reports each one assigned or passed as the interface, but a type matched only through a type assertion or a type switch stops matching at run time, and nothing fails to compile.
Validation: Run
bash <installed-skill-dir>/scripts/check-interface-compliance.shfrom the project when a heuristic scan would help. It lists an exported interface implemented in its own package when nothing in the package converts a value to the interface (an assignment, return, argument, composite-literal element, send, or conversion), and one an exported function returns while no function or method in the package takes it as a parameter (the Bad case above;returned_byin--json). A generic interface is matched through the instantiations the package names; generated files and whatgo ./...skips (testdata,vendor,_and.names) are not scanned. Ask first whether a consumer needs the interface, then review the conditions above. A finding is not a requirement to add an assertion.
Receiver Type
Default to a pointer receiver. Don't mix receiver types on a single type
(recvcheck in the gate reports it) — if any method needs a pointer, use
pointers for all methods. Value
receivers fit maps, funcs, channels, slices that are not resliced or
reallocated, and small immutable structs or basic types;
RECEIVER-TYPE.md has the full decision list.
One exception: a value type whose only pointer methods are decoders keeps its
encoders and String on the value, as time.Time and netip.Addr do. Moved
to *T, they are skipped for a value that is not addressable — a field of a
struct passed by value, a map element — and encoding/json v1 and fmt
print the raw value with no error and no vet finding:
func (c Celsius) MarshalJSON() ([]byte, error) { return json.Marshal(c.String()) } // value
func (c *Celsius) UnmarshalJSON(b []byte) error { ... } // writes *c
var _ json.Marshaler = Celsius{} // a value: (*Celsius)(nil) passes even when values are skippedRelated Skills
- go-naming: the
-ersuffix, receiver names. - go-error-handling: implementing
error, custom error types,errors.Asmatching. - go-generics: whether generics are needed or an interface suffices.
- go-functions: interface-based Option patterns for constructors.
- go-defensive: a typed nil stored in an interface and the other API-boundary pitfalls; compile-time assertions stay here.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

