DEV Community

Andi
Andi

Posted on

Go (Golang) Basic - Interfaces

What Comes After Custom Types?

In the last few parts, we've learned how to create our own data types with structs and attach behavior to them with methods. This is great for creating organized, self-contained components.

But what if we want to write a function that can work with different types, as long as they share a common behavior? For example, a function that can process anything that can be "saved" to a file, whether it's a User struct, a Product struct, or an Invoice struct.

This is where Interfaces come in. An interface is one of Go's most powerful features for writing flexible and abstract code.

1. What is an Interface? (The Contract)

An interface in Go is a type that defines a set of method signatures. It's not a piece of data; it's a contract. It says, "Any type that wants to be considered a member of my group must have these specific methods."

Analogy: An electrical wall socket (stopkontak). The socket is the interface. It has a contract: "Anything that wants to get power from me must have a two-pronged plug that fits my shape." The socket doesn't care if you plug in a phone charger, a fan, or a TV. As long as the device's plug matches the contract, it works.

Defining an Interface

Let's define a simple interface for anything that can make a sound.

package main

import "fmt"

// This is our contract.
// Any type that wants to be a "SoundMaker" MUST have a method
// called MakeSound() that returns a string.
type SoundMaker interface {
    MakeSound() string
}
Enter fullscreen mode Exit fullscreen mode

2. Implementing an Interface (Implicitly)

Here's a key difference between Go and other languages like Java or C#: you don't explicitly say that your type implements an interface.

A type in Go implicitly satisfies an interface if it defines all the methods specified in that interface. No implements keyword needed.

Let's create two different types that will satisfy our SoundMaker contract.

// (Continuing from the code above)

// --- First Type: Dog ---
type Dog struct {
    Name string
}

// Dog now satisfies the SoundMaker interface because it has the MakeSound() method.
func (d Dog) MakeSound() string {
    return "Woof!"
}

// --- Second Type: Cat ---
type Cat struct {
    Name string
}

// Cat also satisfies the SoundMaker interface.
func (c Cat) MakeSound() string {
    return "Meow!"
}

func main() {
    // We'll see how to use these in the next step.
}
Enter fullscreen mode Exit fullscreen mode

Even though Dog and Cat are completely different structs, Go considers both of them to be SoundMakers because they both fulfill the contract.

3. Using the Interface (The Payoff)

Now for the magic. We can create a function that accepts our SoundMaker interface as a parameter. This function doesn't know or care whether it's receiving a Dog or a Cat. It only knows that whatever it receives, it's guaranteed to have a MakeSound() method.

The Flexible Function

Let's create a function that makes any SoundMaker speak.

// (Continuing from the code above)

// This function can accept ANY type that satisfies the SoundMaker interface.
func letThemSpeak(s SoundMaker) {
    fmt.Println("The sound is:", s.MakeSound())
}

func main() {
    // Create an instance of Dog and Cat
    myDog := Dog{Name: "Buddy"}
    myCat := Cat{Name: "Whiskers"}

    // We can pass both myDog and myCat to the same function!
    letThemSpeak(myDog) // Prints: The sound is: Woof!
    letThemSpeak(myCat) // Prints: The sound is: Meow!
}
Enter fullscreen mode Exit fullscreen mode

This is incredibly powerful. It allows us to write generic functions that are decoupled from specific data types, making our code much more reusable and easier to maintain.

Putting It All Together: A Full Example

Here is the complete, runnable program that demonstrates everything we've discussed.

package main

import "fmt"

// 1. The contract (the interface)
type SoundMaker interface {
    MakeSound() string
}

// 2. The first type that satisfies the contract
type Dog struct {
    Name string
}

func (d Dog) MakeSound() string {
    return "Woof!"
}

// 3. The second type that also satisfies the contract
type Cat struct {
    Name string
}

func (c Cat) MakeSound() string {
    return "Meow!"
}

// 4. The flexible function that works with any SoundMaker
func letThemSpeak(s SoundMaker) {
    fmt.Println("The sound is:", s.MakeSound())
}

func main() {
    // Create instances of our concrete types
    myDog := Dog{Name: "Buddy"}
    myCat := Cat{Name: "Whiskers"}

    // Call the same function with different types
    letThemSpeak(myDog)
    letThemSpeak(myCat)
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Interfaces are a core concept in Go for writing clean, abstract, and testable code. By defining contracts (interfaces) instead of depending on concrete types (structs), you can build systems that are much more flexible and scalable.

You've now learned how to:

  • Define an interface as a set of method signatures.
  • Implicitly satisfy an interface by implementing its methods on a struct.
  • Write generic functions that operate on interfaces, not specific types.

In the next part, we'll cover another of Go's most defining features: its elegant approach to Error Handling. See you there!

Top comments (0)