Data structure aka struct
is used to group like data together. For example
humans are made up of data. 😯 And not only are humans made up of data, 😮 that
data can be seen in different views! 😲 What do I mean by this?
Think about it from a doctor’s perspective, what might a doctor take data about? 🤔 Your temperature, your weight, your height, blood pressure, etc.
Now what data does a bank collect from a person? Social Security Number, government issued ID, number of accounts, deposits, withdrawals, etc.
🤯 Crazy, right? Humans are data, not only humans, but everything is data and can be represented that way from all different kinds of views 🤯
So why do we care? As your program grows it will become very, and I mean
very, hard to only have arbitrary string
, rune
, int
, bool
, and float
types running around in your code base. So, instead of declaring thousands of
oddball 🤪 variables, lets group them up, into a logical structure, a data
structure, a struct
😄
Setup
Let’s make our directory structs
and the files we want inside of
that directory example_test.go
structs.go
mkdir structs
touch structs/example_test.go structs/structs.go
Now let’s open up structs.go
and for the very first line we’ll add
package structs
Next for example_test.go
for the very first line we’ll add
package structs_test
We can import basics/structs
into cmd/main.go
and run functions
from there with go run cmd/main.go
and also to run our example_test.go
👍
we use go test structs/example_test.go
in the commandline.
Declare Struct
You know what else is a struct
? A Gopher.
That means you and I can be represented as a struct
. So what basic things
make up a gopher? 🤔 We have a name, we have an age, and there’s a really good
chance we’ll be coding. So let’s do it! 😁
Coding Time!
You’ll notice how nice and neat 🤩 everything lines up. I love ❤️ how it looks
but I wasn’t the one who did it. If you haven’t found it already, Go comes with
a command-line tool simply called go
not too hard to remember 😂. You can use
go fmt structs.go
on the command-line and it will do the pretty formatting
for you. Your text-editor is likely smart enough to do that for you with a
shortcut, why not look around the interwebs for it? Search “format go code
<YOUR-TEXT-EDITOR/IDE-HERE>" 😉
structs.go
// Gopher is a public struct that can be made outside of the package. It
// consists of some basic fields that all gophers have and a privateField that
// can only be accessed in the package. We make things private so the people
// using our library (this code) don't have to worry about certain fields.
type Gopher struct {
Name string
Age int
IsCoding bool
privateField string
}
// city is a private struct with a struct inside of it! 🤯 That's because a
// gopher is a `type` and we can put ALL types into a struct. That means we can
// put city into another struct called state and it would haves cities in it
// with gophers in them! 🤯
type city struct {
gophers []Gopher
gopherAddresses map[Gopher]string
}
New Struct
We’ve made our structs, now it’s time to use them! This process is often referred to as “instantiation”. Big word, doesn’t matter, it means to make.
Instantiate a
Gopher
struct
== Make aGopher
struct
But if you remember in the previous lesson on functions when something is private it can’t be accessed outside of the package! Let’s look at how we would go 😹 about letting others access something private.
Many times when you make a struct
you want to be able to construct it. The
only problem you would face is if you had private fields, like our
privateField
field in our gopher. The problem is you’d have no way of setting
it! As a small taste of what we will code, if I do allow my struct
to be
public and turn it into Gopher
, we still can’t set or privateField
for that
struct
!
noWork := structs.Gopher{
Name: "Goki",
Age: 0xDEAD,
IsCoding: false,
// 👇👇👇 Causes error
privateField: "",
}
We’ll get unknown field privateField in struct literal
. So the way we get
around that is by creating a function that can make our struct
. A constructor
of structs. I say that because often times you will hear New
functions
referred to as constructors, because that’s exactly what they do; construct
a struct
.
It is idiomatic Go to have New
as a constructor name or have the name of the
struct
appended to the function, e.g. func NewGopher( ... ) gopher { ... }
Coding Time!
structs.go
// New is a constructor of a gopher, since New is exported (because it is
// capitalized) we can call it outside of the package, while keeping everything
// about a gopher internal!
func New(name string, age int, isCoding bool, privateField string) gopher {
return gopher{
Name: name,
Age: age,
IsCoding: isCoding,
privateField: privateField,
}
}
example_test.go
func ExampleNew() {
literalGopher := structs.Gopher{
Name: "Gitral",
Age: 0o703,
IsCoding: true,
}
fmt.Printf("Can never set privateField: %#v\n", literalGopher)
constructedGopher :=
structs.New("Jay", 29, true, "once set, can't be changed.")
fmt.Printf("%#v\n", constructedGopher)
constructedGopher.Age = 58
constructedGopher.IsCoding = false
constructedGopher.Name = "Jöt"
// NOTE(jay): Can't do!
// constructedGopher.privateField = "Not possible!"
fmt.Printf("%#v\n", constructedGopher)
// Output:
// Can never set privateField: structs.Gopher{Name:"Gitral", Age:451, IsCoding:true, privateField:""}
// structs.Gopher{Name:"Jay", Age:29, IsCoding:true, privateField:"once set, can't be changed."}
// structs.Gopher{Name:"Jöt", Age:58, IsCoding:false, privateField:"once set, can't be changed."}
}
Basics of Struct
Remember, a struct
is just a way to keep your code neat. It’s not magical 🪄
it’s only for organization. Think about it for the mechanical engineers. They
have raw materials, wood 🪵 metal ⚙️ , and other supporting materials. Would
they just slap concrete into a mix of wood and metal and pray 🙏 that their
structure doesn’t fall? Of course not! That would be a terrible building 🏚️
They plan out how the structure is going to look, what supports it may need,
what loads it could handle, what natural disasters 🌪️ should it stand up to and
then take action.
We want to do the same thing; make our struct
with purpose. It should be
looking to solve some issue. We can solve the issue by grouping them in a
logical structure that makes sense for our current situation.
We’ve introduced it in the example_test.go
but we’ll go into more detail here
about using our structs that we make. We have our data structure, now lets use
it! For right now we’ll just talk about accessing the fields we assign
and also about setting or resetting those fields if they need to be updated.
In that sense, you can think of a struct
as a pre-defined
map
🗺️ with keys that can have different value types! 🤯 We can
then draw the conclusion that struct
gives you more freedom than a map
,
which is great in many situations, but can also be a weakness if all of
your values are the same type
because you’re adding complexity where it
doesn’t belong. Let’s show an example where it would be better to use a map
instead of a struct
type country string
type capital string
// Simple, easy, straight forward and fast.
mCountries := map[country]capital{
"Mongolia": "Ulaanbaatar",
"Burundi": "Gitega",
"Pakistan": "Islamabad",
"Denmark": "Copenhagen",
"Ethiopia": "Addis Ababa",
}
// Complex, hard to follow, hard to understand, construct new structs and
// have no way of accessing country immediately. We have to search entire slice
// before we get our country.
type Country struct {
Name string
Capital string
}
sCountries := []Country{
Country{Name: "Syria", Capital: "Damascus"},
Country{Name: "Georgia", Capital: "Tbilisi"},
Country{Name: "Tanzania", Capital: "Dodoma"},
Country{Name: "Jamaica", Capital: "Kingston"},
Country{Name: "Venezuela", Capital: "Caracas"},
}
Coding Time!
structs.go
// Basic shows you how to initialize (make) structs, manipulate all the
// values within a struct by getting and setting the values and use them in
// other structs. We also return a `city` struct here to show you can give back
// unexported types from exported functions.
func Basic() city {
// Make a gopher and have ALL fields set to the zero value.
var zero Gopher
// Make a gopher and set all fields to what we want them to be.
gordo := Gopher{
Name: "Gordo",
Age: 22,
IsCoding: true,
privateField: "Set it and forget it",
}
// Make a gopher and only set the fields we care about, leaving the rest to
// be initialized (made) with their zero values.
gary := Gopher{Name: "Gary"}
anon := Gopher{Age: 42, IsCoding: true, privateField: "Scanning 60000 ports"}
fmt.Printf("zero valued gopher: %#v\n", zero)
fmt.Printf("gordo gopher: %#v\n", gordo)
fmt.Printf("gary gopher: %#v\n", gary)
fmt.Printf("anon gopher: %#v\n", anon)
fmt.Println()
// Access a value by using the `.` and the fields name
gary.Age = 33
gary.privateField = "Searching: Why does my husband fart so much."
fmt.Printf("gary gopher: %#v\n", gary)
anon.Name = "Garfunkel"
fmt.Printf("anon gopher: %#v\n", anon)
teska := city{
Gophers: []Gopher{gordo, gary, anon},
GopherAddresses: map[Gopher]string{
gordo: "123 Lemon Dr.",
gary: "889 Galaway Ave.",
anon: "543 W 8th St.",
},
}
// Since teska has a slice of gophers we can get it and range over each of
// them in a for loop. g == gopher
for _, g := range teska.Gophers {
// Access each gopher's IsCoding field. In the slice of gophers we are
// accessing from the city!
if g.IsCoding {
fmt.Println(g.Name, "is in the middle of coding! Come back soon.")
continue
}
fmt.Println(g, "lives at", teska.GopherAddresses[g])
}
fmt.Println()
// zero out a gopher, not needed here, but you can see how it is done.
gordo.Age = 0
gordo.Name = ""
gordo.IsCoding = false
gordo.privateField = ""
fmt.Printf("gordo gopher: %#v\nzero gopher: %#v\n", gordo, zero)
return teska
}
example_test.go
Here we can see we can use an unexported struct
🤯 because the function
structs.Basic
returns it. Why do this? It means the person using your
code can’t create a city
but they can get one pre-made and ready to be
used.
func ExampleBasic() {
city := structs.Basic()
for _, g := range city.Gophers {
if g.Name == "Garfunkel" {
fmt.Printf("We found him: %s\n", city.GopherAddresses[g])
}
}
// Output:
// zero valued gopher: structs.Gopher{Name:"", Age:0, IsCoding:false, privateField:""}
// gordo gopher: structs.Gopher{Name:"Gordo", Age:22, IsCoding:true, privateField:"Set it and forget it"}
// gary gopher: structs.Gopher{Name:"Gary", Age:0, IsCoding:false, privateField:""}
// anon gopher: structs.Gopher{Name:"", Age:42, IsCoding:true, privateField:"Scanning 60000 ports"}
//
// gary gopher: structs.Gopher{Name:"Gary", Age:33, IsCoding:false, privateField:"Searching: Why does my husband fart so much."}
// anon gopher: structs.Gopher{Name:"Garfunkel", Age:42, IsCoding:true, privateField:"Scanning 60000 ports"}
// Gordo is in the middle of coding! Come back soon.
// {Gary 33 false Searching: Why does my husband fart so much.} lives at 889 Galaway Ave.
// Garfunkel is in the middle of coding! Come back soon.
//
// gordo gopher: structs.Gopher{Name:"", Age:0, IsCoding:false, privateField:""}
// zero gopher: structs.Gopher{Name:"", Age:0, IsCoding:false, privateField:""}
// We found him: 543 W 8th St.
}