Video Coming Soon...
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:
func
-- Start a function like normal(
-- NEW This starts that you want this function to be part of a struct.name
-- NEW This is the variable name that will be available to your function automatically.*Type
-- NEW This is the pointer syntax for yourstruct
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.)
-- NEW Ends the method type spec.FuncName
-- Name of your function like normal.(params)
-- Your parameters like normal.ret
-- Your return values like normal.{
-- 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
:
Find
-- Given adata
to find, return theNode
that matches.Delete
-- Given aNode
, find it and remove it from the list.PopEnd
-- Go to the end and remove the lastNode
.
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
- Break It -- Once again find ways to break this code. Probably the easiest is passing bad
Node
values to various functions. - Change It -- How many other operations can you implement? If you're very motivated then try sorting it.
- 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
- Implement DoubleLinkedList in the same way.
- Try to explain why the operations
Delete
andPopEnd
so much easier in aDoubleLinkedList
.
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.