.agents/skills/go-interfaces/SKILL.md
Go interfaces, type assertions, type switches, and embedding from Effective Go. Covers implicit interface satisfaction, comma-ok idiom, generality through interface returns, interface and struct embedding for composition. Use when defining or implementing interfaces, using type assertions/switches, or composing types through embedding.
npx skillsauth add phant-app/phant go-interfacesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Go's interfaces enable flexible, decoupled designs through implicit satisfaction and composition. This skill covers interface fundamentals, type inspection, and Go's approach to composition over inheritance.
Source: Effective Go
Interfaces in Go specify behavior: if something can do this, it can be used
here. Types implement interfaces implicitly—no implements keyword needed.
// io.Writer interface - any type with this method satisfies it
type Writer interface {
Write(p []byte) (n int, err error)
}
A type satisfies an interface by implementing its methods:
type ByteSlice []byte
// ByteSlice now implements io.Writer
func (p *ByteSlice) Write(data []byte) (n int, err error) {
*p = append(*p, data...)
return len(data), nil
}
// Can be used anywhere io.Writer is expected
var w io.Writer = &ByteSlice{}
fmt.Fprintf(w, "Hello, %s", "World")
A type can implement multiple interfaces simultaneously:
type Sequence []int
// Implements sort.Interface
func (s Sequence) Len() int { return len(s) }
func (s Sequence) Less(i, j int) bool { return s[i] < s[j] }
func (s Sequence) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// Implements fmt.Stringer
func (s Sequence) String() string {
sort.Sort(s)
return fmt.Sprint([]int(s))
}
By convention, one-method interfaces use the method name plus -er suffix:
Reader, Writer, Formatter, Stringer.
A type assertion extracts the concrete value from an interface.
value.(typeName)
The result has the static type typeName. The type must be either:
var w io.Writer = os.Stdout
f := w.(*os.File) // Extract *os.File from io.Writer
Without checking, a failed assertion causes a runtime panic. Use the comma-ok idiom to test safely:
str, ok := value.(string)
if ok {
fmt.Printf("string value is: %q\n", str)
} else {
fmt.Printf("value is not a string\n")
}
If the assertion fails, str is the zero value (empty string) and ok is
false.
To check if a value implements an interface without using the result:
if _, ok := val.(json.Marshaler); ok {
fmt.Printf("value %v implements json.Marshaler\n", val)
}
A type switch discovers the dynamic type of an interface variable using the
.(type) syntax:
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T\n", t)
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
It's idiomatic to reuse the name in the switch expression. This declares a new variable with the same name but the correct type in each case branch.
Type switches can match both concrete types and interface types:
type Stringer interface {
String() string
}
var value interface{}
switch str := value.(type) {
case string:
return str // str is string
case Stringer:
return str.String() // str is Stringer
}
If a type exists only to implement an interface with no exported methods beyond that interface, don't export the type—return the interface from constructors.
// Good: Constructor returns interface type
func NewHash() hash.Hash32 {
return &myHash{} // unexported type
}
// The implementation is hidden; callers only see hash.Hash32
The crypto/cipher package demonstrates this pattern:
type Block interface {
BlockSize() int
Encrypt(dst, src []byte)
Decrypt(dst, src []byte)
}
type Stream interface {
XORKeyStream(dst, src []byte)
}
// Returns Stream interface, hiding implementation details
func NewCTR(block Block, iv []byte) Stream
Benefits:
Combine interfaces by embedding them:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// ReadWriter combines Reader and Writer
type ReadWriter interface {
Reader
Writer
}
A ReadWriter can do what a Reader does and what a Writer does—it's a
union of the embedded interfaces.
Rule: Only interfaces can be embedded within interfaces.
Go uses embedding for composition instead of inheritance. Embedding promotes methods from the inner type to the outer type.
// bufio.ReadWriter embeds Reader and Writer
type ReadWriter struct {
*Reader // *bufio.Reader
*Writer // *bufio.Writer
}
Without embedding, you'd need forwarding methods:
// Without embedding - tedious boilerplate
type ReadWriter struct {
reader *Reader
writer *Writer
}
func (rw *ReadWriter) Read(p []byte) (n int, err error) {
return rw.reader.Read(p)
}
With embedding, methods are promoted automatically. bufio.ReadWriter satisfies
io.Reader, io.Writer, and io.ReadWriter without explicit forwarding.
Mix embedded and named fields:
type Job struct {
Command string
*log.Logger
}
// Job now has Print, Printf, Println methods
job.Println("starting now...")
The type name (without package qualifier) serves as the field name:
// Access the embedded Logger directly
job.Logger.SetPrefix("Job: ")
Define a method on the outer type to override the embedded method:
func (job *Job) Printf(format string, args ...interface{}) {
job.Logger.Printf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}
Key difference: When an embedded method is invoked, the receiver is the inner type, not the outer one. The embedded type doesn't know it's embedded.
type ReadWriter struct {
*Reader
*Writer
}
// When rw.Read() is called, the receiver is the Reader, not ReadWriter
X on the outer type hides
any X in embedded typesMost interface conversions are checked at compile time. But sometimes you need to verify implementation explicitly.
Use a blank identifier assignment to verify a type implements an interface:
// Verify *RawMessage implements json.Marshaler at compile time
var _ json.Marshaler = (*RawMessage)(nil)
This causes a compile error if *RawMessage doesn't implement json.Marshaler.
Use compile-time checks when:
// In your package
type MyType struct { /* ... */ }
func (m *MyType) MarshalJSON() ([]byte, error) { /* ... */ }
// Compile-time check - fails if MarshalJSON signature is wrong
var _ json.Marshaler = (*MyType)(nil)
Don't add these checks for every interface implementation—only when there's no other static conversion that would catch the error.
Advisory: Go Wiki CodeReviewComments
Choosing whether to use a value or pointer receiver on methods can be difficult. If in doubt, use a pointer, but there are times when a value receiver makes sense.
time.Time with no
mutable fields and no pointers work well as value receiversint, string, etc.// Value receiver: small, immutable type
type Point struct {
X, Y float64
}
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// Pointer receiver: method mutates receiver
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
// Pointer receiver: contains sync.Mutex
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Increment() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
Don't mix receiver types. Choose either pointers or struct types for all available methods on a type. If any method needs a pointer receiver, use pointer receivers for all methods.
// Good: Consistent pointer receivers
type Buffer struct {
data []byte
}
func (b *Buffer) Write(p []byte) (int, error) { /* ... */ }
func (b *Buffer) Read(p []byte) (int, error) { /* ... */ }
func (b *Buffer) Len() int { return len(b.data) }
// Bad: Mixed receiver types
func (b Buffer) Len() int { return len(b.data) } // inconsistent
| Concept | Pattern | Notes |
|---------|---------|-------|
| Implicit implementation | Just implement the methods | No implements keyword |
| Type assertion | v := x.(Type) | Panics if wrong type |
| Safe type assertion | v, ok := x.(Type) | Returns zero value + false |
| Type switch | switch v := x.(type) | Variable has correct type per case |
| Interface embedding | type RW interface { Reader; Writer } | Union of methods |
| Struct embedding | type S struct { *T } | Promotes T's methods |
| Access embedded field | s.T or s.T.Method() | Type name is field name |
| Interface check | var _ I = (*T)(nil) | Compile-time verification |
| Generality | Return interface from constructor | Hide implementation |
data-ai
Beginner-first implementation planning from an approved design. Use after brainstorming, or when the user already approved requirements and wants a step-by-step Laravel implementation plan before coding.
development
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
development
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
development
React composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.