Interfaces
Interfaces in Go
An interface in Go specifies a method set and allows you to define the behavior that types must implement. Interfaces are a cornerstone of polymorphism in Go.
1. Declaring an Interface
An interface is declared using the type
keyword, followed by its name and method set.
Example: Simple Interface
package main
import "fmt"
// Define an interface
type Shape interface {
Area() float64
Perimeter() float64
}
// Implement the interface in a type
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
func main() {
var s Shape = Rectangle{Width: 5, Height: 10}
fmt.Println("Area:", s.Area())
fmt.Println("Perimeter:", s.Perimeter())
}
2. Empty Interface
The empty interface (interface{}
) can hold values of any type. It is commonly used when the type is not known at compile time.
Example: Using interface{}
package main
import "fmt"
func describe(i interface{}) {
fmt.Printf("Type: %T, Value: %v\n", i, i)
}
func main() {
describe(42)
describe("hello")
describe(true)
}
3. Type Assertions
Type assertions are used to extract the underlying value of an interface.
Syntax:
Example: Safe Type Assertion
package main
import "fmt"
func main() {
var i interface{} = "hello"
// Safe assertion
if str, ok := i.(string); ok {
fmt.Println("String value:", str)
} else {
fmt.Println("Not a string")
}
}
4. Type Switch
A type switch allows you to handle multiple types stored in an interface.
Example: Type Switch
package main
import "fmt"
func process(i interface{}) {
switch v := i.(type) {
case string:
fmt.Println("String value:", v)
case int:
fmt.Println("Integer value:", v)
default:
fmt.Println("Unknown type")
}
}
func main() {
process("hello")
process(42)
process(3.14)
}
5. Interface Composition
Go supports interface composition, allowing you to combine multiple interfaces into one.
Example: Composed Interface
package main
import "fmt"
// Define two interfaces
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// Compose Reader and Writer
type ReadWriter interface {
Reader
Writer
}
type File struct {
content string
}
func (f *File) Read() string {
return f.content
}
func (f *File) Write(data string) {
f.content = data
}
func main() {
var rw ReadWriter = &File{}
rw.Write("Hello, Go!")
fmt.Println("Content:", rw.Read())
}
6. Nil Interfaces
An interface with a nil
value behaves differently based on its underlying type.
Example: Nil Interface Behavior
package main
import "fmt"
type Printer interface {
Print()
}
type ConsolePrinter struct{}
func (c *ConsolePrinter) Print() {
fmt.Println("Printing...")
}
func main() {
var p Printer
if p == nil {
fmt.Println("Interface is nil")
}
p = &ConsolePrinter{}
p.Print()
}
7. Practical Use Cases
- Polymorphism: Interfaces allow functions to work with multiple types that implement the same interface.
- Dependency Injection: Pass interfaces to functions to increase testability and decouple implementations.
- Mocking: Use interfaces to mock dependencies in tests.
8. Common Interface Examples in Go's Standard Library
Interface | Description |
---|---|
fmt.Stringer |
Defines the String() string method for custom string formatting. |
io.Reader |
Represents types that can read data. |
io.Writer |
Represents types that can write data. |
error |
Represents types that can return error messages (Error() string ). |
Example: fmt.Stringer
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("Name: %s, Age: %d", p.Name, p.Age)
}
func main() {
p := Person{Name: "Alice", Age: 25}
fmt.Println(p)
}
Best Practices with Interfaces
- Keep Interfaces Small: Prefer single-method interfaces (e.g.,
io.Reader
) for flexibility. - Name Interfaces after Behavior: Use verbs or roles like
Reader
,Writer
, orCloser
. - Design for Contracts: Ensure interfaces represent meaningful contracts for types.
- Avoid Empty Interfaces: Use
interface{}
sparingly; prefer concrete types when possible.