Fundamentals 17 min read

Deep Dive into Go’s Core Mechanisms: Pointers, defer, Polymorphism, and Empty Interface in Practice

This article systematically explores Go's most confusing features—including pointer semantics, defer execution order, lightweight object‑oriented patterns via struct embedding, interface‑based polymorphism, and the empty interface with type assertions—through over twenty runnable code snippets that reveal the underlying design principles.

Golang Shines
Golang Shines
Golang Shines
Deep Dive into Go’s Core Mechanisms: Pointers, defer, Polymorphism, and Empty Interface in Practice

Pointer Usage

The article starts by questioning whether a function that receives a parameter by value can modify the caller's variable. A simple example shows that assigning to the parameter inside changeValue(p int) does not affect the original variable a, because the function works on a copy stored at a different memory address.

package main
import "fmt"
func changeValue(p int) {
    p = 10
}
func main() {
    var a = 1
    changeValue(a)
    fmt.Println(a) // prints 1
}

To actually modify the caller's variable, the article demonstrates passing a pointer:

package main
import "fmt"
func changeValue(p *int) {
    *p = 10
}
func main() {
    var a = 1
    changeValue(&a)
    fmt.Println(a) // prints 10
}

It explains that &a yields the address (e.g., 0x1000), which is stored in the pointer p. Dereferencing *p accesses that memory and updates the original value.

A swap example using two pointers illustrates how pointer parameters enable cross‑scope modification:

package main
import "fmt"
func swap(a *int, b *int) {
    var temp int
    temp = *a
    *a = *b
    *b = temp
}
func main() {
    a := 10
    b := 20
    swap(&a, &b)
    fmt.Println("a=", a, "b=", b) // a= 20 b= 10
}

defer Statement

The article introduces defer with a basic example that prints two messages and defers a third. The output shows that the deferred call runs after the function body finishes.

package main
import "fmt"
func main() {
    defer fmt.Println("main end")
    fmt.Println("hello 1")
    fmt.Println("hello 2")
}

Output:

hello 1
hello 2
main end

When multiple defer statements are present, they execute in last‑in‑first‑out (LIFO) order, forming a stack:

package main
import "fmt"
func main() {
    defer fmt.Println("main end 1")
    defer fmt.Println("main end 2")
    defer fmt.Println("main end 3")
    fmt.Println("hello 1")
    fmt.Println("hello 2")
}

Output:

hello 1
hello 2
main end 3
main end 2
main end 1

The article also compares defer with return. In a function where a deferred call and a return statement coexist, the return value is first assigned, then all deferred calls run (LIFO), and finally the function returns.

package main
import "fmt"
func deferFunc() int {
    fmt.Println("defer func")
    return 0
}
func returnFunc() int {
    fmt.Println("return func")
    return 0
}
func returnAndDefer() int {
    defer deferFunc()
    return returnFunc()
}
func main() {
    returnAndDefer()
}

Output:

return func
defer func

Object‑Oriented Features

Go achieves encapsulation through naming conventions: exported fields start with an uppercase letter, while lowercase fields are package‑private. The article shows a Hero struct with exported fields and getter/setter methods.

package main
import "fmt"
type Hero struct {
    Name  string
    Ad    int
    Level int
}
func (this *Hero) GetName() string { return this.Name }
func (this *Hero) SetName(newName string) { this.Name = newName }
func (this *Hero) Show() {
    fmt.Println("Name is", this.Name)
    fmt.Println("Ad is", this.Ad)
    fmt.Println("Level is", this.Level)
}
func main() {
    hero := Hero{Name: "zhangs", Ad: 100, Level: 10}
    hero.SetName("lis")
    hero.Show()
}

Inheritance is simulated by embedding a struct anonymously. The article defines a base Hero and a derived HeroMan that embeds Hero. Three construction styles—direct literal, field‑by‑field assignment, and a factory function—are demonstrated, all producing the same output.

package main
import "fmt"
// Base struct
type Hero struct {
    name string
    age  int
}
// Derived struct embeds Hero
type HeroMan struct {
    Hero
    level int
}
func (this *HeroMan) Print() {
    fmt.Println("name:", this.name)
    fmt.Println("age:", this.age)
    fmt.Println("level:", this.level)
}
func NewHeroMan(name string, age, level int) *HeroMan {
    return &HeroMan{Hero{name, age}, level}
}
func main() {
    // Literal embedding
    h1 := HeroMan{Hero{name: "zhangs", age: 10}, 100}
    h1.Print()
    fmt.Println("=============")
    // Field assignment
    var h2 HeroMan
    h2.name = "lis"
    h2.age = 20
    h2.level = 200
    h2.Print()
    fmt.Println("=============")
    // Factory function
    h3 := NewHeroMan("wangw", 20, 100)
    h3.Print()
}

Polymorphism via Interfaces

Go implements polymorphism through interfaces. An interface declares a set of method signatures; any type that implements all those methods satisfies the interface implicitly (duck typing). The article defines an AnimalIF interface and two concrete types Cat and Dog that each implement the required methods.

package main
import "fmt"
type AnimalIF interface {
    Sleep()
    GetColor() string
    GetType() string
}
type Cat struct { color string }
func (c *Cat) Sleep()               { fmt.Println("cat sleep") }
func (c *Cat) GetColor() string      { fmt.Println("color is", c.color); return c.color }
func (c *Cat) GetType() string       { fmt.Println("The type is Cat"); return "Cat" }
type Dog struct { color string }
func (d *Dog) Sleep()               { fmt.Println("Dog sleep") }
func (d *Dog) GetColor() string      { fmt.Println("color is", d.color); return d.color }
func (d *Dog) GetType() string       { fmt.Println("The type is Dog"); return "Dog" }
func showAnimals(a AnimalIF) {
    a.Sleep()
    fmt.Println("Kind:", a.GetType())
    fmt.Println("Color:", a.GetColor())
}
func main() {
    cat := Cat{"black"}
    dog := Dog{"Green"}
    fmt.Println("=======================")
    showAnimals(&cat)
    fmt.Println("=============")
    showAnimals(&dog)
}

The article summarizes the mechanism:

Interfaces define behavior contracts without implementation.

Any type that implements all methods satisfies the interface implicitly.

Interface variables hold a pair of pointers (dynamic type and value) enabling dynamic dispatch at runtime.

Go favors composition over class inheritance; embedding structs reuses code without an "is‑a" relationship.

Using interfaces decouples callers from concrete types, allowing new types to be added without modifying existing code.

Empty Interface and Type Assertions

The empty interface interface{} contains no methods, so every type satisfies it. Internally it stores two pointers: one to type metadata and one to the actual value. Before Go 1.18 it was the primary way to write generic‑like code.

package main
import "fmt"
func myFunc(arg interface{}) {
    fmt.Println(arg)
    if value, ok := arg.(string); !ok {
        fmt.Println("arg is not string")
    } else {
        fmt.Println("arg is string : ", value)
    }
}
type Book struct { name string }
func main() {
    book := Book{"GoLang"}
    myFunc(book)   // passes a struct
    myFunc(100)    // passes an int
    myFunc("Hello golang") // passes a string
}

Key points highlighted:

The empty interface acts as a universal container for values of any type.

Type assertions ( v, ok := x.(T)) safely extract the concrete value, avoiding panics.

Direct assertions without the , ok form will panic if the type does not match.

This pattern is useful when a function must handle unknown types, as shown in myFunc.

Overall, the article demonstrates how Go’s pointer semantics, deferred execution, lightweight object‑oriented patterns, interface‑based polymorphism, and the empty interface together enable high‑extension, low‑coupling code—embodying Go’s "less is more" design philosophy.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

GoPointerspolymorphismtype assertioninterfacesdeferempty interface
Golang Shines
Written by

Golang Shines

We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.