Table of Contents
- Structs, Interfaces & Generics
- Contents
- Structs
- Struct tags
- Embedding
- Methods & receivers
- Method sets
- Interfaces
- Type assertions
- Type switches
- Generics
- Constraints
Structs, Interfaces & Generics¶
Contents¶
- Structs
- Struct tags
- Embedding
- Methods & receivers
- Method sets
- Interfaces
- Type assertions
- Type switches
- Generics
- Constraints
Structs¶
type Point struct {
X, Y int
}
p := Point{1, 2} // positional (fragile)
p = Point{X: 1, Y: 2} // keyed (preferred)
p = Point{X: 1} // Y defaults to 0
var z Point // {0 0}
pp := &Point{1, 2} // pointer to struct
pp.X = 9 // auto-deref; same as (*pp).X
// Anonymous struct (one-off)
cfg := struct {
Name string
Port int
}{Name: "web", Port: 80}
// Comparable if all fields are comparable
Point{1,2} == Point{1,2} // true
// Empty struct: zero bytes, used for sets and signal channels
done := make(chan struct{})
set := map[string]struct{}{}
Struct tags¶
Backtick string metadata read via reflection (used by encoding/json, etc.).
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"` // omit if zero value
Password string `json:"-"` // never marshal
Internal string `json:"-,"` // field literally named "-"
Age int `json:"age,string"` // encode number as string
Created time.Time `json:"created" db:"created_at"` // multiple tags
}
Tag format: key:"value,opt1,opt2" key2:"...". Read manually:
Embedding¶
Composition by embedding a type without a field name. Promotes the embedded type's fields and methods.
type Animal struct{ Name string }
func (a Animal) Speak() string { return a.Name + " makes a sound" }
type Dog struct {
Animal // embedded (anonymous field)
Breed string
}
d := Dog{Animal{"Rex"}, "Lab"}
d.Name // "Rex" (promoted field)
d.Speak() // promoted method
d.Animal.Name // explicit access
// Override by redefining the method on the outer type:
func (d Dog) Speak() string { return d.Name + " barks" }
// Interface embedding builds bigger interfaces:
type ReadWriter interface {
io.Reader
io.Writer
}
// Embedding an interface in a struct = "satisfy it, panic if unimplemented"
type partialReader struct{ io.Reader } // only need Read; Writer unset
Embedding is NOT inheritance: the outer type doesn't become the inner type, and method dispatch is static (no virtual override through the embedded type).
Methods & receivers¶
type Counter struct{ n int }
func (c Counter) Value() int { return c.n } // value receiver: gets a COPY
func (c *Counter) Inc() { c.n++ } // pointer receiver: mutates
c := Counter{}
c.Inc() // Go auto-takes &c because c is addressable
c.Value() // 1
// Methods can be on any named type, not just structs:
type Celsius float64
func (c Celsius) String() string { return fmt.Sprintf("%.1f°C", c) }
Rule of thumb: use a pointer receiver if the method mutates, the struct is large, or for consistency when any method needs a pointer. Don't mix value and pointer receivers on the same type.
Method sets¶
What's in a type's method set determines which interfaces it satisfies.
| Receiver type | Method set of T |
Method set of *T |
|---|---|---|
func (t T) |
included | included |
func (t *T) |
NOT included | included |
type Stringer interface{ String() string }
func (c *Counter) String() string { return "..." }
var s Stringer
s = &Counter{} // OK: *Counter has String
s = Counter{} // COMPILE ERROR: Counter's method set lacks String
Key consequence: a value type only satisfies an interface if all required methods have value receivers. Map elements and other non-addressable values can't auto-take an address, so pointer-receiver methods aren't callable on them.
Interfaces¶
Implicitly satisfied — no implements keyword. If a type has the methods, it satisfies the interface.
type Shape interface {
Area() float64
Perimeter() float64
}
type Rect struct{ W, H float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }
var sh Shape = Rect{3, 4} // Rect satisfies Shape automatically
// The empty interface holds any value (Go 1.18+ alias: `any`)
var x any = 42
x = "now a string"
// Compile-time satisfaction assertion:
var _ Shape = (*Rect)(nil) // fails to compile if *Rect stops satisfying Shape
An interface value is a (type, value) pair. A nil interface has both nil. Gotcha: an interface holding a nil concrete pointer is NOT == nil:
func get() error {
var e *MyErr = nil
return e // returns a non-nil interface! (type=*MyErr, value=nil)
}
get() != nil // true — classic bug; return `nil` explicitly
Keep interfaces small; "accept interfaces, return structs."
Type assertions¶
Extract the concrete type from an interface.
var i any = "hello"
s := i.(string) // "hello"; PANICS if i isn't a string
s, ok := i.(string) // comma-ok form: ok=false, s="" instead of panic
n, ok := i.(int) // ok=false, n=0
// Assert to another interface:
if rc, ok := r.(io.ReadCloser); ok { rc.Close() }
Type switches¶
func describe(i any) string {
switch v := i.(type) {
case nil:
return "nil"
case int:
return fmt.Sprintf("int %d", v) // v is int
case string:
return fmt.Sprintf("string %q", v) // v is string
case []byte, []rune:
return "byte or rune slice" // v is `any` here (multiple types)
case fmt.Stringer:
return v.String() // matches any Stringer
default:
return fmt.Sprintf("unknown %T", v)
}
}
Generics¶
Type parameters (Go 1.18+). Square brackets declare them.
// Generic function
func Map[T, U any](s []T, f func(T) U) []U {
r := make([]U, len(s))
for i, v := range s { r[i] = f(v) }
return r
}
doubled := Map([]int{1,2,3}, func(x int) int { return x*2 })
strs := Map([]int{1,2}, func(x int) string { return strconv.Itoa(x) })
// Type inference usually lets you omit the brackets at call sites.
// Explicit: Map[int, string]([]int{1}, conv)
// Generic type
type Stack[T any] struct{ items []T }
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 { return zero, false }
v := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return v, true
}
var st Stack[string]
Constraints¶
A constraint is an interface used as a type bound. It can list methods, types, or both.
import "golang.org/x/exp/constraints" // or define your own
// Method constraint
type Stringer interface{ String() string }
func Join[T Stringer](xs []T) string { /* ... */ }
// Type set constraint (the | union; ~ allows named types with that underlying)
type Number interface {
~int | ~int64 | ~float64
}
func Sum[T Number](xs []T) T {
var total T
for _, x := range xs { total += x }
return total
}
type MyInt int
Sum([]MyInt{1, 2}) // works thanks to ~int
// Built-in constraints (package "cmp" and predeclared):
// any — alias for interface{}
// comparable — supports == and != (map keys, equality)
func Index[T comparable](s []T, target T) int {
for i, v := range s { if v == target { return i } }
return -1
}
// constraints.Ordered — supports < <= >= > (ints, floats, strings)
import "cmp"
func Max2[T cmp.Ordered](a, b T) T { if a > b { return a }; return b } // Go 1.21+
Notes:
~T(approximation) includes any type whose underlying type isT.comparablepermits==;cmp.Orderedpermits ordering operators.- Type parameters can't be used for method declarations beyond the receiver's own params, and there are no generic methods (only generic functions/types).