Go Interfaces: Definition and Implementation
Interfaces are central to Go’s design. They enable polymorphism and abstraction through implicit implementation.
Interface Definition
Basic Interface
package main
import "fmt"
// Define interface
type Writer interface {
Write(data string) error
}
// Implement interface
type FileWriter struct {
filename string
}
func (fw *FileWriter) Write(data string) error {
fmt.Printf("Writing to file %s: %s\n", fw.filename, data)
return nil
}
func main() {
var w Writer = &FileWriter{filename: "output.txt"}
w.Write("Hello, Go!")
}
Multiple Methods
package main
import "fmt"
type Reader interface {
Read() (string, error)
}
type Writer interface {
Write(data string) error
}
type ReadWriter interface {
Reader
Writer
}
type File struct {
name string
}
func (f *File) Read() (string, error) {
return fmt.Sprintf("Content of %s", f.name), nil
}
func (f *File) Write(data string) error {
fmt.Printf("Writing to %s: %s\n", f.name, data)
return nil
}
func main() {
var rw ReadWriter = &File{name: "data.txt"}
content, _ := rw.Read()
fmt.Println(content)
rw.Write("New content")
}
Implicit Implementation
Duck Typing
package main
import "fmt"
type Shape interface {
Area() float64
}
type Circle struct {
radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.radius * c.radius
}
type Rectangle struct {
width, height float64
}
func (r Rectangle) Area() float64 {
return r.width * r.height
}
func printArea(s Shape) {
fmt.Printf("Area: %.2f\n", s.Area())
}
func main() {
circle := Circle{radius: 5}
rect := Rectangle{width: 4, height: 6}
printArea(circle) // Works without explicit implementation
printArea(rect) // Works without explicit implementation
}
Empty Interface
Using interface
package main
import "fmt"
func printValue(v interface{}) {
fmt.Println(v)
}
func main() {
printValue(42)
printValue("hello")
printValue(3.14)
printValue(true)
printValue([]int{1, 2, 3})
}
Type Assertion
package main
import "fmt"
func describe(v interface{}) {
switch val := v.(type) {
case int:
fmt.Printf("Integer: %d\n", val)
case string:
fmt.Printf("String: %s\n", val)
case float64:
fmt.Printf("Float: %.2f\n", val)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}
func main() {
describe(42)
describe("hello")
describe(3.14)
}
Interface Composition
Embedding Interfaces
package main
import "fmt"
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
type ReadWriter interface {
Reader
Writer
}
type Device struct {
data string
}
func (d *Device) Read() string {
return d.data
}
func (d *Device) Write(data string) {
d.data = data
}
func main() {
var rw ReadWriter = &Device{data: "initial"}
fmt.Println(rw.Read())
rw.Write("updated")
fmt.Println(rw.Read())
}
Common Interfaces
Stringer Interface
package main
import "fmt"
type Person struct {
name string
age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d years old)", p.name, p.age)
}
func main() {
p := Person{name: "Alice", age: 30}
fmt.Println(p) // Uses String() method
}
Error Interface
package main
import (
"fmt"
)
type CustomError struct {
message string
code int
}
func (e *CustomError) Error() string {
return fmt.Sprintf("[%d] %s", e.code, e.message)
}
func main() {
var err error = &CustomError{
message: "Something went wrong",
code: 500,
}
fmt.Println(err)
}
Best Practices
โ Good: Small Interfaces
// DO: Define small, focused interfaces
type Reader interface {
Read() ([]byte, error)
}
type Writer interface {
Write([]byte) (int, error)
}
โ Bad: Large Interfaces
// DON'T: Create large interfaces with many methods
type Everything interface {
Read() ([]byte, error)
Write([]byte) (int, error)
Close() error
Seek(int64, int) (int64, error)
// ... many more methods
}
โ Good: Accept Interfaces
// DO: Accept interfaces, return concrete types
func Process(r Reader) ([]byte, error) {
return r.Read()
}
โ Good: Use Type Assertion Safely
// DO: Check type assertion result
if str, ok := v.(string); ok {
fmt.Println(str)
} else {
fmt.Println("Not a string")
}
Summary
Go interfaces provide:
- Implicit implementation without explicit declaration
- Polymorphism through duck typing
- Composition through interface embedding
- Type safety with type assertions
- Flexibility for writing generic code
These features make interfaces powerful tools for abstraction in Go.
Comments