Video Coming Soon...

Created by Zed A. Shaw Updated 2025-10-07 14:32:54

21: struct and Methods: Basics

WARNING This exercise is in DRAFT status, so there may be errors. If you find any, please email me at help@learncodethehardway.com so I can fix them.

We're now going to learn to combine a struct with functions to create methods. A "method" in many languages usually means "function with attached data" and Go's version of the concept is deliciously simple. First, imagine if you had a simple function to print a Person name:

View Source file ex21a/main.go Only

package main
import "fmt"

type Person struct {
    Name string
}

func PersonPrintName(who Person) {
    fmt.Println("Name:", who.Name)
}

func main() {
    zed := Person{"Zed"}
    PersonPrintName(zed)
}

Go makes this easier by letting you "attach" functions to the Person type like this:

View Source file ex21b/main.go Only

package main
import "fmt"

type Person struct {
    Name string
}

func (who *Person) PrintName() {
    fmt.Println("Name:", who.Name)
}

func main() {
    zed := Person{"Zed"}
    zed.PrintName()
}

This lets you use zed.PrintName() and not have to worry about passing in the zed variable, and cleans up the name of the function. Go also gives you the who variable in your function body without having to pass it in on every function call.

The format for this is:

func (name *Type) FuncName(params) ret {
    /// body
}

It's nearly identical but let's break down that first line:

  1. func -- Start a function like normal
  2. ( -- NEW This starts that you want this function to be part of a struct.
  3. name -- NEW This is the variable name that will be available to your function automatically.
  4. *Type -- NEW This is the pointer syntax for your struct and indicates it should attach to that type. You don't have to use a pointer here, but it's more common to do this.
  5. ) -- NEW Ends the method type spec.
  6. FuncName -- Name of your function like normal.
  7. (params) -- Your parameters like normal.
  8. ret -- Your return values like normal.
  9. { -- You guessed it, your function body start like normal.

That's all you need to do. Just add the type for the function and Go takes care of the rest.

About NewX Functions

There's a common idiom to have a New function to correctly create a struct. In our example of a Person it would be this:

func NewPerson(name string) *Person {
    return &Person{name}
}

EDITOR I had this description, there's something I'm missing.

This creates a new Person, gives it the name, but the tricky part is the & at the front. In many languages this would result in a crash, but Go is smart enough to let you return the address of the Person you just created. Go gets away with this because it uses a Garbage Collector, so where a variable is created mostly doesn't matter. In a language like C or C++ this would be a huge failure.

You can also write a NewPerson using the new() builtin if you want to build up the struct in pieces:

func NewPerson(name string) *Person {
    who := new(Person)
    who.name = name
    return who
}

Notice that I did return who and NOT return &who. That's because new(Person) returns a pointer so you're ready to go. Just modify it and return it.

Challenge Time

Time for another challenge. In the last exercise you created a Linked List manually. In this exercise I've created the start of a LinkedList that uses this new methods system.

This code is incomplete because you can only Add and PopFront the contents. Your job is to add the following operations to the LinkedList:

For each of these operations you're going to need to look at how Dump and PopFront work, then figure out how to make each of these work. One huge clue is: "track the previous."

The Code

Here's the code for you, and if you want to try to make the LinkedList totally on your own then go do that and come back to "cheat" when you get stuck.

View Source file ex21/main.go Only

package main

import (
    "fmt"
)

type Node struct {
    Data any
    Next *Node
}

type LinkedList struct {
    Front *Node
    End *Node
}

func (l *LinkedList) Add(data any) {
    el := Node{data, nil}

    if l.Front == nil {
        l.Front = &el
        l.End = &el
    } else {
        l.End.Next = &el
        l.End = &el
    }
}

func (l *LinkedList) PopFront() (*Node) {
    el := l.Front

    if l.Front == nil {
        return nil
    } else if l.Front == l.End {
        l.Front = nil
        l.End = nil
    } else {
        l.Front = l.Front.Next
    }

    return el
}

func (l *LinkedList) Dump() {
    fmt.Println("----------------")

    for cur := l.Front; cur != nil; cur = cur.Next {
        fmt.Println(cur.Data)
    }
}

func main() {
    list := new(LinkedList)
    list.Add("hello")
    list.Add("world")

    fmt.Println("Front is", list.Front.Data)
    fmt.Println("End is", list.End.Data)

    front := list.PopFront()
    fmt.Println("Popped front is", front.Data)

    fmt.Println("New front is", list.Front.Data)
    fmt.Println("New end is", list.End.Data)

    end := list.PopFront()
    fmt.Println("Popped end/front is", end.Data)

    fmt.Println("Now Front is", list.Front, "and End is", list.End)
}

The Practice

  1. Break It -- Once again find ways to break this code. Probably the easiest is passing bad Node values to various functions.
  2. Change It -- How many other operations can you implement? If you're very motivated then try sorting it.
  3. Recreate It -- As usual, either recreate it this from your own description/memory, or find another data structure and do a new implementation. See the Study Drill for a suggestion.

Study Drills

  1. Implement DoubleLinkedList in the same way.
  2. Try to explain why the operations Delete and PopEnd so much easier in a DoubleLinkedList.
Previous Lesson Next Lesson

Register for Learn Go the Hard Way

Register today for the course and get the all currently available videos and lessons, plus all future modules for no extra charge.