In Go, interface conformance is not declared explicitly but is implicitly ensured. A type satisfies an interface if it implements all the methods declared in the interface. Go provides mechanisms to check if a type implements an interface both at compile time and runtime using type assertions and type switches.
Let's start with a basic example of defining an interface and a type that conforms to it.
package main
import "fmt"
// Define an interface
type Animal interface {
Speak() string
}
// Define a type that implements the interface
type Dog struct {
Name string
}
// Implement the Speak method for the Dog type
func (d Dog) Speak() string {
return "Woof!"
}
func main() {
// Create an instance of Dog
var a Animal = Dog{Name: "Buddy"}
// Call the Speak method
fmt.Println(a.Speak()) // Output: Woof!
}
You can perform compile-time conformance checking by assigning a variable of your type to a variable of the interface type. If your type does not implement the interface, the compiler will produce an error.
package main
// Define an interface
type Animal interface {
Speak() string
}
// Define a type that implements the interface
type Cat struct {
Name string
}
// Implement the Speak method for the Cat type
func (c Cat) Speak() string {
return "Meow!"
}
// A function that requires the Animal interface
func makeAnimalSpeak(a Animal) {
println(a.Speak())
}
func main() {
// This will work since Cat implements Animal
makeAnimalSpeak(Cat{Name: "Whiskers"})
// This will cause a compile-time error if uncommented, because int does not implement Animal
// makeAnimalSpeak(5)
}
You can use type assertions to check at runtime if a variable of an interface type holds a specific concrete type.
package main
import "fmt"
// Define an interface
type Animal interface {
Speak() string
}
// Define a type that implements the interface
type Bird struct {
Name string
}
// Implement the Speak method for the Bird type
func (b Bird) Speak() string {
return "Chirp!"
}
func main() {
var a Animal = Bird{Name: "Tweety"}
// Type assertion to check if a is of type Bird
bird, ok := a.(Bird)
if ok {
fmt.Printf("a is of type Bird and says: %s\\\\n", bird.Speak()) // Output: a is of type Bird and says: Chirp!
} else {
fmt.Println("a is not of type Bird")
}
}
A type switch is a convenient way to handle multiple type assertions.
package main
import "fmt"
// Define an interface
type Animal interface {
Speak() string
}
// Define types that implement the interface
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return "Meow!"
}
type Bird struct {
Name string
}
func (b Bird) Speak() string {
return "Chirp!"
}
func main() {
var a Animal = Dog{Name: "Buddy"}
// Type switch to determine the actual type of a
switch v := a.(type) {
case Dog:
fmt.Printf("a is a Dog named %s and says: %s\\\\n", v.Name, v.Speak())
case Cat:
fmt.Printf("a is a Cat named %s and says: %s\\\\n", v.Name, v.Speak())
case Bird:
fmt.Printf("a is a Bird named %s and says: %s\\\\n", v.Name, v.Speak())
default:
fmt.Println("a is of an unknown type")
}
}
By using these mechanisms, you can ensure that your types conform to the expected interfaces both at compile time and runtime, thus leveraging Go's powerful type system and interface mechanics.