Interfaces
## Learning Objectives
- Understand interfaces as contracts
- Implement interfaces implicitly
- Use empty interface
- Master type assertions and switches
- Design with interfaces
## What is an Interface?
An interface defines a contract. Types implementing an interface must provide all specified methods.
## Declaration
### Basic Interface
```go
type Speaker interface {
Speak() string
}
```
### Multiple Methods
```go
type Animal interface {
Speak() string
Move() string
}
```
## Implementation
### Implicit Implementation
Types implement interfaces implicitly - no explicit declaration.
```go
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof!"
}
func main() {
var s Speaker = Dog{Name: "Buddy"}
fmt.Println(s.Speak()) // Woof!
}
```
### Both Implementations
```go
type Cat struct{}
func (c Cat) Speak() string {
return "Meow!"
}
type Cow struct{}
func (c Cow) Speak() string {
return "Moo!"
}
func main() {
animals := []Speaker{Cat{}, Cow{}}
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}
```
## Empty Interface
### interface{} (empty interface)
```go
var i interface{} = "hello"
i = 42
i = true
```
### Accept Anything
```go
func printAll(items ...interface{}) {
for _, item := range items {
fmt.Println(item)
}
}
printAll(1, "hello", true, 3.14)
```
## Type Assertions
### Basic Assertion
```go
var i interface{} = "hello"
s := i.(string) // Panics if not string
fmt.Println(s) // hello
s, ok := i.(string) // Safe: ok is false if not string
fmt.Println(s, ok) // hello true
```
### Assert Wrong Type
```go
var i interface{} = 42
s, ok := i.(string)
fmt.Println(s, ok) // "" false (no panic)
```
### Panic on Failure
```go
var i interface{} = 42
s := i.(string) // PANIC: interface conversion failed
```
## Type Switch
### switch on Type
```go
func describe(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Integer: %d\n", v)
case string:
fmt.Printf("String: %s (length %d)\n", v, len(v))
case bool:
fmt.Printf("Boolean: %t\n", v)
case float64:
fmt.Printf("Float64: %.2f\n", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}
func main() {
describe(42)
describe("hello")
describe(true)
}
```
### Type Switch with Interface
```go
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func printArea(s Shape) {
switch shape := s.(type) {
case Circle:
fmt.Printf("Circle with radius %.2f, area %.2f\n", shape.Radius, shape.Area())
case Rectangle:
fmt.Printf("Rectangle %.2f x %.2f, area %.2f\n", shape.Width, shape.Height, shape.Area())
default:
fmt.Println("Unknown shape")
}
}
```
## Interface Values
### Structure
An interface value has two components:
1. Dynamic type
2. Dynamic value
```go
var s Speaker
fmt.Printf("%+v\n", s) //
s = Dog{Name: "Buddy"}
fmt.Printf("%+v\n", s) // {Name:Buddy}
```
### nil Interface
```go
var s Speaker = nil
if s == nil {
fmt.Println("Interface is nil")
}
```
### Interface with nil Value
```go
var s Speaker = (*Dog)(nil)
if s == nil {
fmt.Println("Interface is nil")
} else {
fmt.Println("Interface is not nil, but value is nil")
}
```
## Errors
### error Interface
```go
type error interface {
Error() string
}
```
### Custom Error
```go
type DivisionError struct {
Dividend, Divisor float64
}
func (e *DivisionError) Error() string {
return fmt.Sprintf("cannot divide %.2f by zero", e.Dividend)
}
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, &DivisionError{Dividend: a, Divisor: b}
}
return a / b, nil
}
```
### Using Errors
```go
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
```
## io.Reader and io.Writer
### io.Reader
```go
type Reader interface {
Read(p []byte) (n int, err error)
}
```
### io.Writer
```go
type Writer interface {
Write(p []byte) (n int, err error)
}
```
### Using Readers
```go
import "io"
import "strings"
reader := strings.NewReader("Hello, World!")
buf := make([]byte, 5)
for {
n, err := reader.Read(buf)
if err == io.EOF {
break
}
fmt.Printf("Read %d bytes: %s\n", n, buf[:n])
}
```
### Using Writers
```go
import "os"
file, err := os.Create("output.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
n, err := file.Write([]byte("Hello, World!"))
fmt.Printf("Wrote %d bytes\n", n)
```
## Stringer
### fmt.Stringer
```go
type Stringer interface {
String() string
}
```
### Stringer Implementation
```go
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d)", p.Name, p.Age)
}
func main() {
p := Person{Name: "Alice", Age: 30}
fmt.Println(p) // Alice (30)
}
```
## Interface Composition
### Embedding Interfaces
```go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
type ReadCloser interface {
Reader
Closer
}
```
## Best Practices
### Accept Interfaces, Return Structs
```go
func NewService(repo Repository) Service {
return &service{repo: repo}
}
type Service interface {
DoSomething() error
}
```
### Keep Interfaces Small
```go
// Small interface (preferred)
type Writer interface {
Write([]byte) (int, error)
}
// Large interface (avoid)
type Everything interface {
Read([]byte) (int, error)
Write([]byte) (int, error)
Close() error
// ...
}
```
## nil Interface Check
```go
func doSomething(i interface{}) {
if i == nil {
fmt.Println("nil interface")
return
}
fmt.Printf("Value: %v, Type: %T\n", i, i)
}
```
## Summary
- Interfaces define contracts (sets of methods)
- Types implement interfaces implicitly
- Empty interface `interface{}` accepts any type
- Type assertions: `i.(T)` with comma-ok idiom
- Type switch: `switch v := i.(type)`
- error interface: `Error() string`
- io.Reader and io.Writer are common interfaces
- Accept interfaces, return concrete types
- Keep interfaces small and focused
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →