Menu
Coddy logo textTech

Interface Satisfaction Rules

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 43 of 107.

While Go uses duck typing for implicit interface satisfaction, there are specific rules that determine whether a type actually satisfies an interface. Understanding these rules helps you avoid subtle bugs.

The most important rule involves pointer receivers. If a method is defined with a pointer receiver, only a pointer to that type satisfies the interface—not the value itself:

type Saver interface {
    Save() string
}

type Document struct{ Name string }

func (d *Document) Save() string {  // pointer receiver
    return "Saved: " + d.Name
}

func Process(s Saver) {
    fmt.Println(s.Save())
}

Because Save() has a pointer receiver, only *Document satisfies Saver:

func main() {
    doc := Document{Name: "report.txt"}
    
    Process(&doc)  // works - pointer satisfies interface
    // Process(doc)  // compile error - value doesn't satisfy interface
}

However, the reverse is more flexible. If a method has a value receiver, both the value and pointer types satisfy the interface. Go automatically dereferences pointers when calling value receiver methods:

func (d Document) Info() string {  // value receiver
    return d.Name
}

// Both Document and *Document satisfy an interface requiring Info()

This asymmetry exists because Go can always get a value from a pointer (by dereferencing), but it cannot always get a pointer from a value (the value might not be addressable). Keeping this rule in mind prevents confusing compiler errors when working with interfaces.

challenge icon

Challenge

Easy

Let's build a configuration system that demonstrates how pointer and value receivers affect interface satisfaction. You'll create types where the receiver choice determines whether values, pointers, or both can be used with an interface.

You'll organize your code across three files:

  • config.go: Define a Configurable interface that requires two methods: GetValue() string and SetValue(string). Then create two configuration types:
    • ReadOnlyConfig with a Value field—use a value receiver for GetValue() (returns the Value) and a pointer receiver for SetValue() (updates the Value)
    • Setting with a Data field—use value receivers for both methods (GetValue returns Data, SetValue just prints "Cannot modify" without changing anything)
  • processor.go: Create a function called ProcessConfig that accepts a Configurable and a new value string. It should print the current value using GetValue(), call SetValue() with the new value, then print the value again to show any changes.
  • main.go: Read configuration details from input and demonstrate the interface satisfaction rules:
    1. Create a ReadOnlyConfig and pass a pointer to ProcessConfig (required because SetValue has a pointer receiver)
    2. Create a Setting and pass the value directly to ProcessConfig (works because both methods have value receivers)

The following inputs will be provided:

  • Line 1: Initial value for ReadOnlyConfig
  • Line 2: New value to set for ReadOnlyConfig
  • Line 3: Initial value for Setting
  • Line 4: New value to attempt for Setting

Your ProcessConfig function should print in this format:

Current: [value]
Current: [value after SetValue]

For example, given debug, production, localhost, and remote, your output should be:

Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhost

Notice how ReadOnlyConfig actually changes its value (because we passed a pointer), while Setting remains unchanged (its SetValue with a value receiver can't modify the original). The key insight is that ReadOnlyConfig values alone wouldn't satisfy Configurable—only pointers do—while Setting values work directly because all its methods use value receivers.

Cheat sheet

Go uses duck typing for implicit interface satisfaction, but there are specific rules about pointer and value receivers:

Pointer Receiver Rule

If a method has a pointer receiver, only a pointer to that type satisfies the interface:

type Saver interface {
    Save() string
}

type Document struct{ Name string }

func (d *Document) Save() string {  // pointer receiver
    return "Saved: " + d.Name
}

func main() {
    doc := Document{Name: "report.txt"}
    
    Process(&doc)  // works - pointer satisfies interface
    // Process(doc)  // compile error - value doesn't satisfy interface
}

Value Receiver Rule

If a method has a value receiver, both the value and pointer types satisfy the interface:

func (d Document) Info() string {  // value receiver
    return d.Name
}

// Both Document and *Document satisfy an interface requiring Info()

Why This Asymmetry Exists

Go can always get a value from a pointer (by dereferencing), but cannot always get a pointer from a value (the value might not be addressable).

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Read inputs
	scanner.Scan()
	rocInitial := scanner.Text()
	scanner.Scan()
	rocNew := scanner.Text()
	scanner.Scan()
	settingInitial := scanner.Text()
	scanner.Scan()
	settingNew := scanner.Text()
	
	// TODO: Create a ReadOnlyConfig with rocInitial value
	// TODO: Pass a POINTER to ProcessConfig (required because SetValue has pointer receiver)
	
	// TODO: Create a Setting with settingInitial value
	// TODO: Pass the VALUE directly to ProcessConfig (works because both methods have value receivers)
	
	// Use these variables to avoid unused variable errors
	_ = rocInitial
	_ = rocNew
	_ = settingInitial
	_ = settingNew
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming