Afrikaans
Akan
Albanian
Amharic
Arabic
Armenian
Azerbaijani
Basque
Belarusian
Bemba
Bengali
Bihari
Bosnian
Breton
Bulgarian
Cambodian
Catalan
Cebuano
Cherokee
Chichewa
Chinese (Simplified)
Chinese (Traditional)
Corsican
Croatian
Czech
Danish
Dutch
English
Esperanto
Estonian
Ewe
Faroese
Filipino
Finnish
French
Frisian
Ga
Galician
Georgian
German
Greek
Guarani
Gujarati
Haitian Creole
Hausa
Hawaiian
Hebrew
Hindi
Hmong
Hungarian
Icelandic
Igbo
Indonesian
Interlingua
Irish
Italian
Japanese
Javanese
Kannada
Kazakh
Kinyarwanda
Kirundi
Kongo
Korean
Krio (Sierra Leone)
Kurdish
Kurdish (Soranî)
Kyrgyz
Laothian
Latin
Latvian
Lingala
Lithuanian
Lozi
Luganda
Luo
Luxembourgish
Macedonian
Malagasy
Malay
Malayalam
Maltese
Maori
Marathi
Mauritian Creole
Moldavian
Mongolian
Myanmar (Burmese)
Montenegrin
Nepali
Nigerian Pidgin
Northern Sotho
Norwegian
Norwegian (Nynorsk)
Occitan
Oriya
Oromo
Pashto
Persian
Polish
Portuguese (Brazil)
Portuguese (Portugal)
Punjabi
Quechua
Romanian
Romansh
Runyakitara
Russian
Samoan
Scots Gaelic
Serbian
Serbo-Croatian
Sesotho
Setswana
Seychellois Creole
Shona
Sindhi
Sinhalese
Slovak
Slovenian
Somali
Spanish
Spanish (Latin American)
Sundanese
Swahili
Swedish
Tajik
Tamil
Tatar
Telugu
Thai
Tigrinya
Tonga
Tshiluba
Tumbuka
Turkish
Turkmen
Twi
Uighur
Ukrainian
Urdu
Uzbek
Vietnamese
Welsh
Wolof
Xhosa
Yiddish
Yoruba
Zulu
welcome to this comprehensive go
programming course for beginners
throughout this course you'll learn key
Concepts and techniques to write
performant idiomatic go code you will be
guided by the expertise of Lane Wagner
and Alan lers who combined have over 18
years of coding experience alongside
mastering topics such as variables
function loops and more you'll also have
the opportunity to apply your new found
skills in seven real world projects
ranging from building an RSS aggregator
to implementing authentication with API
keys so get ready to unlock the
incredible potential of go go has been
exploding in popularity recently it
feels like all the most modern tech
companies are using go to build scalable
backend infrastructure it actually makes
a lot of sense Go's fast lightweight has
an amazing developer experience and is
actually super easy to learn stick
around and in just a few minutes I'll
explain the rest of the reasons why go
could be a game changer for your coding
career at this point I'll just introduce
myself really quickly I'm Lane the
founder of boot. Dev and I've been
writing go for a little over 7 years and
I've been building software for about 10
I've actually spent over 2 years
designing this go course and I've taught
thousands of students with this material
the feedback from all of my students
over the last couple years is actually
been Incorporated in the course so
everything is very battle tested and up
todate I was actually just making some
updates yesterday so how does this
course actually work well we're going to
start by doing over 100 handson coding
lessons and exercises now when we're
done with all of that you'll actually
have a really strong grasp on the
fundamentals so at that point we'll go
build a production ready back-end server
Ino from scratch now I'm begging you
please do not binge watch this video
tutorial hell is a very real place and
it's a place that you will go if you
don't write your own code get your hands
on the keyboard and write some code with
me in fact you should actually be coding
ahead of me and only using my Solutions
when you get stuck so head over to boot.
and create a free account that's where
all of the code samples for this course
are hosted now alternatively I have
linked a GitHub repo in the description
below all of the raw code for the
samples in this course are hosted there
it won't be quite a streamline of an
option but it is an option now you
should also know that this course is
just one part of the full back-end
developer career path over on boot. so
if you're interested in going from zero
to hired as a backend developer you
should definitely check that out too now
as long as we're talking about external
resources know that if you get stuck
during this course you have some options
for help first you've got the boot. deev
Discord second you've got the free code
Camp Discord and third you've got the
free code Camp Forum I will link all of
those down in the description below okay
one last thing before we jump into the
course if you want to connect with me
personally or you want access to my
other go and backend content then you
can follow me on Twitter wag lane or you
can subscribe to my YouTube channel at
boot. I'll link both of those down in
the description below with all of that
out of the way let's talk about go
everyone always wants to talk about how
fast the go programming language is
let's talk about it and let's compare go
to some of the other more popular
programming languages so in terms of
execution speed
execution
speed go is much faster than JavaScript
python Ruby and PHP pretty much any
interpreted language a language that's
not compiled is going to be slower than
go because go is a compiled language now
don't worry too much if you're not
familiar with the terms compiled and
interpreted we'll talk about those in
just a minute for now just understand
that go is much faster um than these
languages when it comes to executing
programs if we're doing computationally
heavy work go is going to be much more
performant um than some of these other
languages now here on the other side
I've listed some compiled languages so
when it comes to compilation
speed compilation
speed go is actually much faster than
these compiled languages when it comes
to compiling the code now again we'll
talk about compiling in a minute but for
now just understand that you have to
compile your code before you can can run
it when you're working with a compiled
language and so by having a fast
compilation speed like go has it
actually increases developer
productivity quite a bit we can iterate
more quickly on our code we can deploy
it more quickly it's not as expensive to
run tests to compile uh the program and
deploy it to production um so this is
actually a huge benefit that go has now
I do want to point out that go does not
necessarily run faster it does not
necessarily have a faster execution
speed than all of these languages but it
does beat them handily when it comes to
compiling not to beat a dead horse but I
want to talk a little bit more about
execution speed so we talked about how
go is generally faster than the
interpreted languages right python
JavaScript Ruby PHP and so on it gets a
little interesting when we compare go to
kind of the natively compiled languages
or the languages that compile directly
to uh kind of machine code that runs on
your CPU versus the compiled languages
that run on top of a virtual machine
right so the the two big ones that run
on a virtual machine are Java and C
while some of the compiled or natively
compiled languages that you'll be
familiar with might be rust C C++ even
though go is a natively compiled
language a language that compiles
directly to machine code like rust C and
C++ its execution speed is actually more
similar to Java and C when it comes to
its runtime speed how many computations
it can do kind of per second and we'll
talk more about this later but the
primary reason for that is the go run
time there's basically a chunk of code
that's included in every go program that
manages memory and that tends to slow
down the execution speed a little bit
that said it is worth pointing out that
a go program tends to use much less
memory than Java and csharp because
there isn't a need for an entire virtual
machine here we are the first coding
challenge let me break down what we are
supposed to do and then I'll kind of
explain what this code over on the right
actually does so um our assignment is to
log the string starting textio server to
the console instead of hello world so
throughout this course we'll be building
out little pieces of the textio product
which if you're familiar with twilio
it'll kind of be like a twilio clone
it's a kind of backend server that sends
SMS and email messages and works a lot
with kind of text textual data every
file of go code has a package
declaration at the top here we have
package main simply because this program
builds into an executable go program
right so we can run this code kind of
Standalone the next line is importing
the fmt package from the standard
Library we are importing it because
we're using it down here within the main
function now the main function is the
entry point to the program so every go
program starts exec ution at the top of
the main function which is just a
function named main that takes no inputs
and doesn't return anything on line six
and seven we have some single line
comments these don't execute they are
not part of the program they're just
there for documentation and single line
comments just start with that double
slack finally on line eight we have the
one thing that this program actually
does which is print the string hello
world to the console so let me go ahead
and run
that see down here it printed to the
console it's using the stand libraries
fmt package um and the print line
function that is exposed from that
package uh to do so so the assignment
here is pretty simple uh we're just
supposed to swap out that hello world
message for starting Tex iio server
going to run that and make sure it looks
like what I would
expect we're good to go next let's fix a
quick bug I love quick wins um we're not
going to talk about all of this syntax
in this program what we're interested in
is just fixing the math bug on line 17
so the assignment description says texo
users are reporting that we're billing
them for wildly inaccurate amounts
they're supposed to be build
for2 for each text message they
send something else is happening so the
total cost here is being set to the cost
per message plus the number of messages
now that doesn't make a lot of sense to
me go ahead and pause the video see if
you can figure this one out on your own
um to me it looks like the cost per
message should be multiplied by the
number of messages right and let me let
me run it like this so that we can see
Doris spent 4.02 on text messages
today four
messages that doesn't make sense right
for four messages she should not be
build $42 if each message only cost 2
cents so I'm going to go ahead and
change that to multiply I would expect 8
cents yep Dora spent 8 cents on text
messages today cool going to go ahead
and submit that this is one of my
favorite XKCD Comics uh you can pause
and read it uh really quick if you'd
like slow and resource expensive
compilation times a really terrible
thing to work with I've worked on
systems in Java and C++ that took over
an hour to compile the code that means
that if we find a bug and we want to
deploy it to production even if we get
the bug fixed within five minutes it's
still going to take an hour just to
build the new production version so that
we can deploy it on our servers I've
personally never worked on a go program
that's taken more than just a couple of
seconds to build and compile so the
first question is go code generally runs
blank than interpreted languages and
compiles blank than other compiled
languages like C and rust so the answer
is going to be faster and faster right
it runs faster than inter most
interpreted languages and it compil Iles
faster than most compiled
languages so the question on this one is
does go generally execute faster than
rust the answer is going to be no so
I've thrown around this word compilation
or compiled a few times but we haven't
really talked about what it means when
we write
code we write it in a human readable
format right typically in a file in the
case of go A.O file right maybe main.go
and this file is going to contain
human readable text right go code that
we as developers work on the thing is
your computer's Hardware doesn't know
what any of that human readable text
means your computer's CPU only
understands
binary right which is ones and
zeros right which at the end of the day
is just numbers right but it's just
numbers and simple operations things
like add and subtract so we need some
process that we can use to convert human
readable code to machine code that can
be executed by the computer's hardware
and that's all that compilation is
compilation is just the process of
taking some human readable code right go
code in our instance and converting it
to Binary or machine code that our
computer can actually understand so what
does this process actually look like
Well normally you'd start by writing
your go program so say main.go
and you'd run it through the go compiler
on your command line you'd type go
build and that would produce a new file
which is the executable program let's
say we're doing a hello word a hello
world program it might be
hello
world.exe
right an executable program on your
computer and you could run that
executable directly on your operating
system without ever having to use the go
tool chain again so the great thing is
you can take this hello world.exe
program and give it to someone else and
they can run it on their computer
without ever having to install the go
tool chain or even know that you used go
to build the program in the first place
this is different than languages like
python where if you want to run
someone's python code you have to use
the python interpreter every single time
time and you run the source code you run
the source code directly on your machine
it's also worth pointing out that part
of the reason that compiling is so much
faster at runtime is that we do all of
this compiling work upfront so when we
go to run the executable we don't have
to do any conversions from Human
readable text to Binary machine code
that's different than how interpreted
languages work with an interpreted
language as we run the program The
Interpreter is reading the human code
and kind of at runtime converting it to
machine code that the CPU can operate on
so you might be wondering where is the
compiling Happening Here on botev well
we actually do the compiling and the
running at the same time for you so when
you click the Run button um we're
actually taking your code shipping it
off to our servers compiling it running
it and giving you the result don't worry
when we get down the road to the actual
project you'll be building and running
your own go code on your own machine but
we can see the difference between a
compiler error and a runtime error even
here on Bev so for example um here in
this code the assignment says to pass
this exercise fix the compiler error in
the code so I'm just going to run it as
is and you'll see we get this nasty
error here and it says main.go right
line six syntax error this is actually a
compile time error so we weren't even
able to compile this code it didn't fail
at runtime it failed compile time right
which again that distinction will become
more clear a little bit later so anyways
um we can fix it by adding that Clos
parentheses that was
missing and now it compiles and runs
just
fine so to review go code like this
little go program here that prints hello
world that's not understood directly by
the hardware on your computer your
computer's processor or your computer's
CPU
understands machine codee so we need to
take our human readable go code run it
through the go compiler to produce the
machine code that we can run directly on
our CPU right the CPU is designed by the
manufacturer to run a specific format of
binary cool so the question for this
exercise is do computer processors
understand English instructions like
open the
browser no processors are not chat GPT
uh they need machine
code now we touched on this very briefly
before but I want to talk about how you
distribute a compiled program versus how
you would distribute an interpreted
program so let's say that you'd written
a script in Python so you've got this
script main.py right this is raw python
code and you want to give it to your
friend so that they can run your script
well all you would do with an
interpreted language is give your friend
the main.py file and then on their
computer they would run the command
Python
main.py and they'd be able to run your
code now there are a couple of downsides
to this approach the first problem is
that your friend needs to have python
installed on their computer so your your
friend being able to use your program is
dependent on them already having python
installed it's even dependent on them
knowing how to use it or knowing how to
use a command line right so Distributing
the programs of interpreted languages
can be tricky because it's really only
useful if you're Distributing to other
developers who know how to use these
tools already the other problem is the
code itself let's say you spent you know
many weeks writing this python script
and it's super useful and you're trying
to sell it to customers if you just give
them your python code I mean they
effectively own it they can change it
even if you didn't intend for it be open
source congratulations it's now open
source the problem is you can't really
allow someone to use your program
without giving them all of the Special
Sauce that makes it work let's review
how this works in a language like go so
in go we'd start the same way right we'd
write some human readable go code in a
file called main.go but instead of
giving that file to our friend we're
going to compile it
first going to compile it
we'd use the go go tool chain so we'd
write something like go build in our
command line and that would produce a
new executable file right so this is
this is machine code here um let's just
say that the name of our program is
Hello World why
not so if we're on Windows it might be
hello world.exe
right so when you go online and you
download a program to use say it's a
video game um it is probably
a bundle of machine code it is a built
binary right in order to run your
favorite video game let's say Starcraft
2 or World of Warcraft you don't need to
install the C++ compiler right you're
just given the built kind of final
product the final executable program and
that's what we're doing in uh the go
programming language as well so now we
can give our friend this
executable they don't need to install go
and they do not need access to the
original source code so generally
speaking Distributing programs that are
natively compiled is much much easier um
than Distributing programs that kind of
have a runtime dependency like an
interpreter so to answer the question
for this exercise do users of compiled
programs need access to the source code
no they don't and a related question
which language is interpreted we've got
go C++ Python and rust and the answer is
python the rest are all compiled so now
the question is why is it generally more
simple to deploy a compiled serers side
program or backend application and the
answers are compiled code is more memory
efficient because Docker exists there
are no runtime language dependencies or
because compiled code is
faster well it's just the same as what
we talked about earlier um when we
deploy to a server say on the cloud uh
we have to have all of the dependencies
uh that our backend application needs in
order to run installed on that server
and if there are no runtime dependencies
if all we need is a compiled binary then
that's arguably the simplest way to do
it right so the answer is it's more
simple to deploy a comp to deploy a
compiled program to a back-end server if
there are no runtime language
dependencies things like the python
interpreter or The nodejs Interpreter
for example go is strongly typed and
statically typed and that's a really
good thing if you've been paying any
attention at all to the JavaScript World
you'll notice that a lot of JavaScript
developers are making the switch to
typescript and that's primarily to get
access to static typing a lot goes into
typing and type systems but one of the
biggest benefits of a static type system
like goes is that when we declare a
string right like say this username
string that I've set equal to wag Lane
we can't later accidentally change it to
an integer a number right like a float
64 um it's going to stay a string the
nice thing about a static type system
like we have with go or that you have
with typescript is that we get feedback
on our errors uh much more quickly right
rather than finding out about a bug when
our code is running in production we
find out about the bug say when we
compile our code let's move on to the
assignment the the assignment says we'll
be using basic authentication for the
text iio API so basic authentication is
is just this format here where you've
got a username and a password um in an
HTTP header it tells the server on each
individual request to the server who you
are right it's kind of like logging in
um or rather it's kind of like being
logged in okay uh so the code on the
right has a type error change the type
of password to a string Okay cool so I'm
just going to run it to see the type
error first so invalid operation
username plus colon plus password has
mismatch types of string and int now
this is a compile time error we we
weren't even able to compile this code
right let alone run it in production um
so we've been instructed in the
assignment change the type of password
to a string but use the same numeric
value so that it can be concatenated
with the username variable Okay cool so
I'm just going to change this to a
string by surrounding in double quotes
that's how you do strings and go
pretty similar to other languages and
then I'm just going to change that type
to a string type and run it again
authorization basic wag Lane colon
number that looks good to
me when we're talking about the
performance of a programming language or
an application we really care about how
it performs across two different axes
one is speed how fast it can do
computations right which is kind of
measured in CPU cycles and then we also
have memory consumption which is just
how bloaty the program is how much data
it has to store in memory to be able to
do those computations every program you
write no matter the language is going to
be using memory every time you create a
new variable it allocates space in
memory where it can store that
variable's data now in languages like
rust or C memory management is
effectively manual now that said Russ
does have some nice tooling that kind of
takes care of it for you at compile time
so it's not quite as error prone um as
as it would be in C or C++ but at the
end of the day your program is
allocating memory right it's saying this
chunk of memory I'm going to use I'm
going to store some variable data here
in this bit of RAM and then later your
program says I'm not using it anymore um
so we can free it up for use by other
programs now let's jump over to Java so
with Java it's a little bit different um
Java is a garbage CL CED language
garbage collection which essentially
means that memory management is
automated and in Java it's done by the
Java virtual machine so every time you
run a Java program you're actually
creating an
entire mini virtual
machine that your Java bite code runs
within so this is the
jvm and then you kind of insert your
code
into the
jvm and you run your code in there and
the jvm is what takes care of allocating
and freeing all of the memory that you
use and this creates overhead basically
at the end of the day Java programs use
quite a bit more memory than rust or C
programs go is in an interesting sort of
in between world where go is a garbage
collected language like Java so it has
automated automated memory management
but it does not have a jvm when you
compile go code rather than having to
run it within a jvm just like with Rus
and C you get one binary or one
executable program the difference is
that go includes a
runtime a
runtime within every single binary
that's built using the go programming
language so we could think of it as
something like this it's kind of like a
little side
car that is compiled
alongside your code so your go program
basically has this this little bit of
extra code that's added to it and that
bit of code is what handles garbage
collection and automated memory
management so it's a little more bloaty
than what you'd get with rust and C
right it is garbage collected um but
it's not nearly as um expensive in terms
of memory overhead as a language like
Java or C let's take a look at what some
actual numbers look like I pulled this
chart from Dexter darwick uh blog and if
you're following Along on botev you can
click the link to go check out the full
the full blog and the description of
this experiment but basically he built a
restful web server in three different
programming languages right in Java go
and rust and then me measured the memory
consumption and when the servers were
just at rest effectively doing nothing
and waiting for requests to come in Rust
used less than half of the memory that
go was using and go used 100 times less
memory right measured in megabytes than
the jvm was using to run the rest
service so to order the three languages
in terms of memory efficiency I would
say Java is the least efficient uh go is
in the middle and rust would be the most
memory efficient and it's also worth
pointing out just really quickly that as
the load on the server increases I would
expect go to have more similar
performance to Java in other words we
wouldn't see as quite a large
discrepancy here right go would likely
still be more memory efficient than Java
almost certainly in fact um but it
probably wouldn't be a 100x discrepancy
we're seeing this huge discrepancy um
mostly because this is an idol uh a test
of an idle program so generally speaking
which language uses more memory that's
going to be Java another question on the
same topic is what's one of the purposes
of the go runtime so to style go code
and make it easier to read that doesn't
make sense that would be like compile
time tooling right not run time tooling
to clean up unused memory to cook fried
chicken or to compile go- code so it's
definitely going to be to clean up
unused memory and if you remember
remember the runtime is just that little
bit of extra code that's included in
every compiled go program that among
other things handles memory
management so we've already talked about
how go has strong and static types but
we haven't yet talked about what those
types are you're probably already
familiar with Boolean values and string
values which are valid go types and also
exist in pretty much every other
programming language um numbers are
where it first starts to get a little
bit different uh if go is your first
compiled programming language generally
speaking numbers fall into four
different buckets we have integers
unsigned integers or uints floats and
complex numbers integers are just whole
numbers they can be positive or negative
right 1 2 3 4 you get the idea unsigned
integers are the same as integers but
they're not signed which basically just
means they don't have a negative
component you can only represent
positive numbers in in an unsigned
integer you probably already familiar
with the idea of floats it's just
fractional numbers right numbers that
have a fractional component things like
1.21 or
3.14 complex numbers are a little funny
they're used to represent the concept of
imaginary numbers if you've gotten to
imaginary numbers in your math studies
I've never actually used complex numbers
um in production I'm sure there are
plenty of use cases for them but we're
not going to go into detail on how they
work here you can certainly go read up
on it if you'd like the only other thing
worth mentioning is that size matters
when it comes to types A uint 8 and a
uint 16 are two different types they
both represent unsigned integers but a u
16 has twice as much room for data
within it it has 16 bits of data whereas
a u 8 only has eight bits for example
the largest number that you can store in
a uint 8 is 255 because that's the
largest number you can represent with
eight 1es and zeros in binary but with a
uint 16 the largest number you can store
is about
65,000 again because that's the biggest
number that can be represented by 16
binary digits just like bigger number
better person uh bigger number in your
types means you can represent more
possible values uh within that type so
float 64 can represent more values than
a float 32 the only reason you wouldn't
use a larger type is if you're trying to
save on memory if you're trying to write
a program that is hyper performant
you'll want to use a smaller size right
if you know that an integer is only ever
going to store three different values
say 1 two or three then you might
consider using a u and 8 the bite type
is an interesting one and it's one that
you'll use a lot especially when you're
say marshalling a Json object um to be
sent across a network connection or
maybe you're reading to and from a file
on disk um but under the hood a bite is
just an Alias for the uint8 type which
makes sense right a bite is just eight
binary digits eight bits and that's all
a uint 8 is a rune is a Unicode code
point which generally speaking you can
think of as one character in a string
and that's usually how it's used under
the hood it's just an alias for the
int32 type moving on to the assignment
it says initialize the given variables
to int float 64 bu and string
respectively with their zero values and
um as we can see here if we just use VAR
the name of the variable and the type
that should do it so we'll initialize
the variables here so
VAR SMS sending limit is an INT VAR cost
per SMS is a float
64
has as permission is
a
and username
is a string okay cool and all so all of
these variables are now instantiated and
should contain their zero value so for
example zero uh 0.0 false and empty
string so let's go ahead and run
that and that looks correct to me I'm
going to go ahead and submit it we've
been declaring variables the hard way
now we're going to do it the easy way
there is an operator in go colon equals
that is the short assignment operator
and it allows us to to declare variables
and have go infer their type so instead
of typing VAR empty string we can just
say empty colon equals the empty string
and go knows that this has to be a
string so it is a string now when we use
this short assignment operator we're not
saying this is a loose type that can
change in the future it's still a static
type empty is a string just like it
would be if we declared it this way in
reality in go you will very rarely see
variables declared like this you will
almost always see them declared using
the short the short assignment operator
for example num Carson equals 10 creates
a new variable called num cars and sets
it equal to 10 and its type will be
inferred to be an INT and the int type
Alias is either int32 or int64 depending
on your computer's architecture if you
want to specify a particular size of
integer then you would declare it using
this kind of lonand syntax so in this
assignment we're just meant to declare a
variable named congrats with the value
happy birthday using a short variable
declaration so as simple as congrats
colon equals the string happy
birthday let me run
that
perfect this next assignment says our
current price to send a text message in
texo is 2 cents however it's likely in
the future that the price will have to
be a fraction of a penny or have a
fractional part to the cost so we should
use a float 64 to store this value edit
the pennies per text declaration so that
it's inferred by the compiler to be a
float 64 Okay cool so
here we're just setting it equal to two
if I run that then I get the type of
pennies per text is int and this percent
T in go um is is a formatting verb that
that tells the go programming language
or or at least the print F function I
should say the formatting package from
the standard Library um that I want to
print the type of this variable rather
than its value so that's why we're
saying int there instead of
two um to get a float all we need to do
is change it from two to 2.0 I believe
64
cool another handy syntactic Quirk of
the go programming language is that we
can declare multiple values on the same
line so in this assignment it says
declare a float called average open rate
and a string called display message on
the same line okay so average open
rate display
message same line the average open rate
um must
be23 and the display message should be
the
string is the average open rate of your
message and then it looks like
this is just going to print them
together so it's going to say 0.23 is
the average open rate okay cool let's go
ahead and run
that 023 is the average open rate of
your messages that looks correct to
me so we've already briefly talked about
the different type sizes in go right so
we have the int type int 8 int 16 int 32
and int 64 and it's important to
understand that the int type just
aliases int32 or int64 um same with the
uint type just depending on your cpu's
architecture so you might be on a 32 or
a 64-bit machine most modern machines
are going to be 64 bits my
recommendation is that unless you have
an explicit reason to care about the
size right so unless you're trying to
kind of hyper optimize for performance
then you should really just stick to
these four types int uint float 64 and
again if you're working with imaginary
numbers then complex 128 but that's
honestly unlikely so so these three
types are going to do the vast majority
of the heavy lifting when it comes to
working with numbers in go we can also
convert numbers between you know
different number types for example we
could take a an integer 88 and convert
it to a float like this so it become
88.0 right um converting the other way
is a little trickier though because say
we had 88.6 if we were to convert it to
an INT we would lose the point 6 we
would truncate it down to just 88 now
this assignment says our text AO
customers want to know how long they've
had accounts with us follow the
instructions in the comment provided you
will create a new variable called
account age int that will be the
truncated integer version of account age
Okay cool so create a new account age
int here should be the result of casting
account age to an integer so we'll just
do int account age and I would expect
that to be two after casting it because
it should truncate the 6 so let's go
ahead and run that your account has
existed for 2 years yeah that looks good
to
me we already briefly touched on this
but it's worth mentioning again I
recommend that you stick to the I call
them the default types so for example we
know there's um you know five or six I
can't think of it off the top of my head
now different types of inss right int
int 8 int 16 in 32 int 64 I'd recommend
sticking to int unless you have a very
good reason to specify a smaller size
like int 8 or in32 and that's simply so
you avoid cluttering your code with tons
of type conversions that can sometimes
even lead to bugs so unless you need a
smaller type for performance reasons
just use these default types okay so the
question for this assignment is when
should you elect to not use a default
type when either a default or a specific
size will work when my system has lots
of extra Hardware that I want to utilize
or when performance and memory are
primary concern so it's going to be
performance if you have performance
concerns that's the time I would maybe
stray away from the default
types the next question is what does the
size of a type indicate so a float 64
what does 64 mean is it bits bites or
nibbles uh the answer is going to be
bits now it is worth pointing out
nibbles is a real thing so a bite is 8
Bits a nibble is actually Four bits if
you didn't know that fun interesting
trivia aside from variables go also
supports constants which are immutable
values um and in go just like in
JavaScript or typescript we use the
const keyword and constants do not
support the short declaration syntax so
we have to kind of write it all
out okay so getting to the assignment
says use two separate constants
something weird is happening in this
code what should be happening is that we
create two separate constants Premium
plan name and basic plan name right now
looks like we're trying to overwrite one
of them okay cool so on line six we've
got Premium plan name and we're setting
it to Premium plan and then we have
Premium plan name again attempting to
override the value and set it to a new
string if we try to run this we actually
get a compile time error so we're not
allowed to mutate constants in go so
what we should be doing is creating a
separate constant and it should be named
basic plan name let's go ahead and run
that plan Premium plan plan basic plan
looks good to me constants in go are not
the same as constants in JavaScript and
in typescript in JavaScript and
typescript the const keyword really just
means you can't reassign to this
variable but you can compute the
variable or the variable's value at
runtime in go every value
that's stored in a constant must be
known or computed at compile time before
the program runs so if we create this
new constant called my int and set it
equal to 15 within the compiled go
binary effectively this symbol my in
just refers to the static number
15 the cool thing is that we can
actually compute constants like we can
make constants that depend on other
constants but that computation will run
when we compile our code not when we run
our code so for example um I can create
this constant first name Lane last name
Wagner and then I can create a new
constant called full name it's first
name plus a space plus and the last name
and that's really convenient in case I
ever want to change first name uh now I
don't have to change it in two places
right full name will automatically
update but this is only valid because
all of the inputs to this full name
constant first name and last name are
known at compile time so the compiler
can still do the thing it wants to do
which is replace full name with a static
string right lane space
Wagner so on to the assignment says
keeping track of time in a message
sending application like textio is
critical imagine getting at a point
reminder an hour after your doctor's
visit not very helpful right complete
the code using a computed constant to
print the number of seconds in an hour
okay so we've got the number of seconds
in a minute is 60 the number of minutes
in an hour is also 60 so how many
seconds are in an hour cool well we
could hardcode this as like 60 *
60 but the cool thing is we can actually
compute it because we can say well we
know the minutes the number of minutes
in an hour and we know the number of
seconds in a
minute and if we multiply those two
together we should get the number of
seconds in an hour
right that looks correct to
me so I'm obviously a huge go fan but
formatting strings in go is honestly one
of my least favorite features of the
language I think it's one of its
weaknesses at the moment who knows maybe
it'll improve in the future the way it's
done well we essentially have two
different functions provided to us by
the standard Library we have print F and
S printf printf prints a formatted
string directly to standard out and S
printf just Returns the formatted string
as a value basically all string
formatting in go currently works the
same way we have these formatting verbs
things like percent V percent s percent
D and they're replaced in the string
template with actual values so for
example I am percent V years old the
percent V in this case is replaced by 10
the first parameter uh that comes after
the template in the print F function
here we could also replace with a string
instead of an integer right I am percent
V years old way too many for I am way
too many years old percent V is sort of
the default formatter it's usually what
you want assume you don't want to print
in kind of the default standard way
using percent V then there are a few
others um percent S interpolates A
String percent D interpolates an integer
in decimal form so for example 10
becomes the number 10 instead of say in
binary form uh percent s is for floats
so you can specify the number of kind of
places after the decimal point uh that
you want printed out to the console or
printed out to The Returned string in
the case of s printf um so I actually do
end up using percent F fairly regularly
when I'm working with floats so onto the
assignment it says create a new variable
called MSG online 9 MSG stands for
message of course um it's a string that
contains the following High name your
open rate is open rate percent where
name is the given name and open rate is
the open rate rounded to the nearest
10's place okay so the 10's place is the
number right after the decimal so let's
get started here message colon equals
fmt dos printf so we're going to use S
printf instead of print F because we
don't want this value going to standard
out we want it returned from the
function so we can save it in the MSG
variable
let's just grab this
template so we'll use percent s we could
use percent s or percent V here because
we're just uh interpolating a string and
we'll do percent um .1 F because we
want to just print the
first number after the the decimal point
right the tenth
place okay and then we just pass the two
values as the following parameters or as
the last two parameters so name and open
rate right so the first value name will
go into the first verb the second value
will go into the second verb let's run
that see what we get hi s Goodman your
open rate is
30.5% that looks good to me let's talk
about conditionals in go so a
conditional is just where we are
checking if a condition is true if it is
we do one thing if it's not we may do
another so for example uh here's an if
statement in go and if this expression
evaluates to true then we'll run the
stuff within the curly braces the body
of the estatement if you're familiar
with other programming languages like
JavaScript this is um a very very
similar syntax the only difference is
we're not surrounding the height is
greater than four section with um
parentheses so again to be clear this
bit between the if keyword and the curly
brace um will be evaluated and if it
evaluates to true then the stuff inside
the curly braces will be executed um so
in this case we have the variable height
and we're using the greater than
operator um to compare it to the number
four so if height is greater than four
then we'll print you are tall enough
sort of listed um some of the different
comparison operators uh down here at the
bottom they are basically identical to
pretty much every other programming
language you will uh probably have used
uh up to this point additionally we can
do different things um if the if
statement does not evaluate to true so
this is a perfectly valid if statement
you do not need an else if or an else
block they are they're optional
effectively um but the way it works is
when we get to this code uh basically
we'll we'll uh compare height to six
right and if that expression evaluates
to true then we'll just print you are
super tall and we'll be done
okay otherwise if that expression
evaluates to false then we'll drop down
into this next if else or sorry else if
statement and we'll compare height to
four if height is greater than four then
we'll print you are tall enough and
again we'll be done at that point so if
this evaluates to true we execute this
um kind of section of code between the
curly braces and at that point we'll be
done otherwise if that's also false so
if height is not great than six and
height is not greater than four then the
else statement executes notice the else
statement does not have its own
expression it just kind of always
executes if all of the if and else if
statements um turned out to be false so
let's jump into the assignment it says
fix the bug on line 12 the if statement
should print message sent if the message
length is less than or equal to the max
message length or message not sent
otherwise Okay cool so up here we've
defined two variables uh message length
is 10 Max message length is 20 um and
then here we're going to do some
comparing let me just run the code in
its current
state says trying to send a message of
length 10 and a max length of 20 message
not sent okay so that seems problematic
right because with a message length of
10 and a Max message length of 20 I
should be able to send that message so
the Bug Online 12 I think we just need
to flip this operator to be less than or
equal to so that now this expression
right message length less than or equal
to Max message length should evaluate to
true because it is message length is in
fact smaller okay let's run
that message sent that seems to work
it's worth pointing out that in go we
also have kind of an alternate way to
write if
statements if the variable that we are
comparing in an if statement is only
used in that if statement then this
syntax can be helpful okay so here's
kind of the traditional way of doing
something we would create a variable
called length let's just assume it's an
integer you can kind of forget um this
function Syntax for now we'll talk about
it um in a future chapter but basically
the idea here is we have a length
variable it's an integer right and we're
initializing it here we're creating the
variable here um and then we're compar
pairing it against one right we're
checking if it's less than one and if it
is we're doing something in this case
we're printing that the email is invalid
well instead of this syntax and by the
way this works perfectly fine there's
nothing wrong with doing it this way um
but we can alternately do it this way
which is basically to initialize that
length variable in the if block within
that first kind of initial statement so
notice there's two statements here um
separated by a
semicolon and in the first one we're
creating that length variable and then
then we're moving the comparison itself
kind of after the semicolon and then if
that condition obviously evaluates to
true then we'll um execute the block
this does kind of two things for us
first it saves us a line of code um
which I would argue probably isn't the
biggest benefit in the world um but more
importantly uh it makes it so that this
length variable is only accessible
within uh kind of the scope of this if
block so kind of down under Neath this
code we wouldn't be able to use the
length uh variable anymore which is kind
of nice if you never intended to use it
in the first place you can kind of think
of this as a clean code hack or like a
um kind of safety hack um to ensure that
the length variable is never reused Down
Below in other code when you never
intended it to be reused okay so uh the
question for this assignment is why
would you use the initial section of an
if statement uh and the answers are to
confuse other programmers to keep the
code concise and the scope limited or to
speed up your code um the answer is
going to be to keep the code more
concise and to again limit the scope
that that variable um exists within like
other programming languages go supports
functions functions are basically just a
way to break up your code into
individual units that are easier to
reason about right a function takes in a
specified number of inputs and it
returns ears a specified number of
outputs for example this subtract
function here named sub takes two inputs
X and integer and Y an integer and it
returns a single integer in this case it
just performs the simple calculation x -
Y and then Returns the result to the CER
now this little bit right here right
Funk sub X integer y integer returns
integer is what's known as the function
signature if you've never heard that
term before it's basically just a
description of what the function does in
terms of its types in terms of its
inputs and its outputs and what types
they are right this basically says this
is a function called sub it takes an
integer X and an integer Y and returns
another integer function signatures are
great because they tell us how we can
use the functions really at the end of
the day if we're the person calling the
function or the person using the
function really all we care about is
what we need to give the function as
input and what we get out of the
function in terms of its outputs so a
function signature basically tells us
all we need to know about a function to
be able to use it it omits all of the
implementation details the stuff with
within the curly brackets or within the
body of the function so let's get on to
the assignment assignment says we often
need to manipulate strings in our
messaging app that makes sense right
we're working with SMS and email uh
messages within textio so we're doing a
lot of textual data manipulation the
concat function should take two strings
as inputs and smash them together right
so returning a new string that is a
concatenation of the inputs for example
hello Plus World equals hello world so
we'd expect to return this concatenated
string from our concat function over
here fix the function the function
signature of concat to reflect its
Behavior Okay cool so let me try running
this and just see what happens looks
like undefined
S1 undefined S2 undefined S1 okay so
these are
undefined
and so S1 and S2 that kind of stands for
string one string two that makes sense
the problem here is that we're
not we're not telling
go what the types of the inputs should
be and this plus operator when operating
on strings just concatenates so that
should work let's go ahead and run
that yeah this looks good to me I want
to just make one more point about
function signatures in go you'll notice
that the type comes
after the name of the variable so S1 is
a string S2 is a string and that's just
to make it a little easier to read the
authors of the go programming language
kind of built on uh a lot of the ideas
from C and in C it was the reverse it
was string S1 string S2 and that just
kind of reads a little clunky if you're
used to kind of speaking in plain
English makes more sense for the type to
kind of come after um what it describes
go provides another bit of interesting
syntactic sugar when it comes to
function signatures when multiple
arguments are of the same type in this
case X and Y the inputs to the add
function are both integers um the type
only needs to be declared on the last
one assuming that they're in order right
so in this case this is valid go code
and X and Y are both integers because
they follow one another we can put the
integer um just after the y if we were
going to add say a string as a third
parameter uh to this function then we
would just add a comma here after int
and put you know name String or or
whatever um this is just a bit of
syntactic sugar uh it makes our code a
little less verbose you don't need to do
this you can explicitly put the type on
every input and output but you will
often see code like this it's a
convenient shorthand so we've got these
two um example Snippets of code Funk
create user first name String last name
String age in and Funk create user first
name last name String agent so which of
the following is the most succinct way
to write a function signature Su synct
is just another word for kind of it's I
guess it's the opposite of verbose right
fewer fewer
words um it's going to be the one that
uses the syntactic sugar which is this
one because we're omitting the string
keyword after first name so we already
talked about this briefly the idea that
in go we specify the type of of a
variable after the variable name and
this is you know different from kind of
the C style way of doing things which if
you were to declare a variable Y and C
you would say int Y and really the
authors of the go programming language
just felt that that didn't flow
naturally from English it's not the way
we talk right we say x is an integer not
integer is X if you want to read up on
that decision and why they ended up uh
choosing that style then you can follow
this link here assuming you're following
Along on botev so the question is what
are we talking about when we discuss
this declaration syntax right this this
swapping of the name and the type um and
here are options the decision about
Campbell case versus snake case um the
style of language used to create new
variables types and functions guard
Clauses versus if else no the ever
important question of tabs versus spaces
no it's going to be the style of
language used to create new variables
types and functions related question is
Which languages declaration syntax reads
like English from right to left C or go
the answer is going to be go go supports
functions as data or basically the idea
that you can pass functions around your
program uh to be called in different
places callbacks right if you're
familiar with JavaScript then you're
probably familiar with the idea of a
callback it's a function that you could
pass to another function to be called
later this question deals with that idea
whenever we pass a call back in go the
type of the function Chang based on what
its inputs and outputs are so for
example here funk in in int is a
function that takes two integers as an
input and returns an integer and that's
going to be a different type than a
function that say took three integers as
input and returned an integer and if you
think about it it makes sense right if
I'm going to pass a function to another
function so that it can call it later it
kind of needs to know how many inputs
how many parameters it can pass into
that function if it's a function that
takes two inputs versus three inputs
then the caller is going to have to
write the code differently so we have to
treat every function signature as its
own unique type so this question is a
bit of a doozy I encourage you to pause
the video and try to work this one out
on your own um but basically it says
what is this hairy beast here right F
Funk Funk int int int int
int all right potential answers are a
function named F that takes as int takes
an INT as the argument and returns an
INT uh no it is not nearly that simple
right a function named F that takes a
function and an INT as arguments and
returns a function let's
see uh that right there is a full
function and an INT as arguments and
returns a function no this returns an
INT okay a function named f that takes a
function and an INT as arguments and
returns an INT that's what I believe it
will be function named F that takes a
function as the argument and returns an
INT yep that's not it so ju to be clear
f is a function it takes two
parameters a
function right a function of a specific
type a function that takes two ins and
returns an INT and as its second
parameter an INT and then F returns an
INT
hopefully that makes sense again feel
free to pause the video and and stare at
that for a second let's talk a little
bit about memory and how data that we
create in our program using variables is
stored in memory so over here I'll keep
track of the memory of our program and
over here I'll write some
code so let's say in our code we write X
colon equals 5 right so we're creating a
new variable called X and we are giving
it the value the integer value of five
and then we're placing the value
five in that memory right stored as
binary data within Ram now in our
program X the symbol is essentially just
a
pointer it points to this location in
memory so let's say on the next line of
code we update X and we say x equal 2 so
now we're reassigning the value of x to
two now let's try something different
what if we create a new variable called
Y and we initialize it to the current
value of x which in this case happens to
be two well in this
case we're actually going to allocate a
new section of memory to store the value
of y and we'll initialize it to the
current value of x which is two
and the symbol
y now has its own location in memory so
we basically created a copy of X now
this idea is really important to
understand because sometimes in
programming we'll have multiple
variables that actually point to the
same location in memory they can
overwrite each other and sometimes we
don't sometimes we have copies of data
right so now for example if I were to
update y let's say I made y equal 1 at
this point x would be unaffected X would
remain two but y now becomes one right
because we have a copy these two
variables X and Y reference different
locations in memory so why does all of
this matter well in go variables are
passed by value not by reference so
let's take a look at this code snippet
here top of main we deare a new variable
called X we set it equal to 5 and then
we pass X into a function called
increment the increment function just
adds one to X right making it six after
that we print X and the weird thing is
that when we print X here we still get
five and the reason for that is the
increment function was operating on a
copy of X when we pass X in here to
increment increment gets a new copy of X
still equal to 5 it increments that X to
6 and then because we are not returning
it it essentially just gets thrown away
and then back in main we still have this
same X that's still equal to five the
correct thing to do here would be to
have the increment function return X
after making its modification and up in
main we would write x equals increment X
so that we could capture the return
value from the increment function so
moving on to the assignment it says it's
critical in texo that we keep track of
how many SMS messages we have sent on
behalf of our clients fix the bug to
accurately track the number of SMS
messages sent okay let me just try
running this in its current
state and we're missing a
return okay I'm going to remove
this this uh type return there and see
what happens sent 430
messages and it looks like here we have
sends so far sends to add increment S is
doing nothing because s so far is still
printing as
430 okay I think I'm understanding so
let me put that back in the assignment
says alter increment sends so that it
Returns the result after incrementing
sends so
far alter the main function to capture
the return value from increment sends
and overwrite the previous sends so far
value Okay cool so this is pretty
similar to the code snippet here
basically we need to return an INT so
we're going to
return send so
far and then here we need to reassign
sense so far into the result of
increment sense cool
so again here we'll still be operating
on copies but because we're going to
return the copy and save it back into
the original variable we should be good
to go let me run
that yep you've sent 455 messages that
looks correct functions in go can have
multiple return values and when they do
have multiple return values the Syntax
for specifying that is just to wrap the
return values in parentheses as well so
when there's just a single return value
we do not wrap that return value in
parenthesis but when there are multiples
we do wrap them in parenthesis one thing
I really like about go is that it does
not allow you to have unused variables
and because it doesn't allow you to have
unused variables and because it allows
you to have multiple return values from
a function we kind of need a way to
ignore some of the return values because
there are definitely instances where a
function returns two things but maybe we
only care about one of those things for
example a point on a graph can be
described by its XY coordinates but
maybe all we care about is the x
coordinate so here we can call the get
Point function and ignore the Y value by
using an underscore and it's important
to understand that the underscore is not
just like a conventional name that we're
going to ignore it's actually ignored
the
compiler completely removes it from our
code so moving on to the assignment here
in texo we have obviously first names
and last names for all of the users that
we're able to send messages to well when
we welcome someone to texo we don't need
their last name so let me go ahead and
try to run this code and you'll see
we'll actually get a compiler error that
says last name declared and not used
used like I said go does not allow us to
have unused variables which I think is
kind of an awesome uh little bit of the
tooling it helps keep our code very
clean and concise easy to understand um
so we need to explicitly ignore that
last name with an underscore if we're
not going to use it let me try running
that again and that looks good to me in
go we can name our return values and if
we do it actually Alters the behavior of
the function
just a little bit let's take a look at
this function get chords or get
coordinates it returns two integers and
we've named the integers X and Y and by
naming them we've actually initialized
at the top of the function the the
variables X and Y and they're
initialized with their zero values so in
the case of an integer literally just
the number zero for both of them the
other interesting thing about naming our
return values is that if we use a naked
return statement a return statement that
doesn't explicitly say for example
return 0 comma 5 then the values of X
and Y are automatically returned from
the function
so this version of get coordinates is
actually the exact same as this kind of
more verbose version of get coordinates
right here we have not given the return
values the names X and Y and instead
we've initialized X and Y to their zero
values and then returned them explicitly
now a couple of recommendations I would
recommend using named returns when you
want to document kind of what the
intended purpose of each return value is
so for example if you have a function
that just returns three integers that
function signature could be pretty
confusing but if you have a function
signature that says it returns three
integers and they're named width height
and length that's a lot more interesting
to the caller of the function they
understand the purpose of each
individual return value much better so I
like to think of named return values as
basically a built-in way of documenting
what the purpose of all of your return
values are and you should generally just
use them on the other hand this implicit
or automatic return that you get along
with um named return values I would
typically advise against you'd only want
to use this in like very short very
simple functions um because it harms
readability right and I'm pulling this
directly from the tour of go they also
agree with me um implicit returns or
naked return statements um generally a
little bit harder to understand so the
way I would write this function
personally would be get chords XY in and
then I would explicitly return X and Y
so let's jump down into the assignment
it says one of our clients likes us to
send text messages reminding users of
Life events coming up fix the bug by
using named return values in the
function signature so the code will
compile and run as intended Okay cool so
this is the function we are interested
in fixing years until events looks like
it takes a user's age as input and then
returns or should return kind of the
number of years until they're an adult
which is 18 the number of years until
they can drink at least in the US which
is 21 and the number of years until they
can rent a car which apparently is 25 um
and it looks like we never want a
negative number so if if any of these
are less than zero we just set them
equal to zero that makes sense once
you're over 18 your years until you're
an adult are just zero right you're
already an adult okay let me try running
this see what we get okay undefined
years until adult all right this makes
sense right because there's no colon
here so we're not defining a new
variable and the assignment said to use
named return values so let's go ahead
and do that so here's until adult years
until drinking I'm going to format this
a little
better and we'll do years until car
rental so again this will declare all of
these values with their zero value at
the top and then this naked return
statement should return them in
order and just to make sure adult
drinking car
rental adult drinking car rental okay
we're in the right order let me run that
first test so four years old they'll be
an adult in 18 years can drink in 17 can
the current 21 that all looks good 10
it's going down
22 Yep this looks good to me so as I
mentioned before explicit returns are
probably better than implicit returns in
most scenarios um it just makes a lot
more sense right so here in this
function get chords XY in so we're using
named return
but we're still explicitly returning X
and Y this is how I would recommend
writing most of your go code
um this this function here is doing the
same thing um it's explicitly returning
hardcoded values though instead of the
variables X and Y and it's just
important to understand that this
effectively overrides the implicit
return of X and Y so in this case five
and six will be returned again that's
why I recommend doing it explicitly
because when you see a return statement
that has explicit values being returned
those are the ones that are returned you
don't have to do any guess work you
don't have to scroll back to the top of
the function to see which values are
being returned so now we're going to
break that advice just for practice's
sake um the assignment says fix the
function to return the named values
implicitly okay so here we have a
problem in our code where we are
basically explicitly returning zeros
which as we talked about overrides the
implicit return so my guess is if we
look at all of these
yep every test is returning zeros if we
just remove that implicit return and run
that then it should work as intended yep
and then just to show you what I mean
like what I would recommend doing is
this it's
bigger like that that's how I'm going to
recommend doing it and in fact because
Boot Dev just checks the output this
should work just fine with our test so
I'm going to submit it like
this moving on to some questions about
named returns so it says when should
naked returns be used and the answers
are for large functions for small
functions or for complex functions and I
would argue if you're going to use naked
returns at all which honestly I'd kind
of just recommend against then you
should only use them for small functions
the more complex and large your
functions get the more important it is
to be explicit and readable and document
your returns with named returns and
things like that the next question is
when should named returns be used so
when there are many values being
returned when the function is simple or
when there are few parameters being
returned I would argue it's never really
a problem to name your return values but
it's really important when there are
many values being returned especially if
there are many values of the same type
being returned because then you can you
know essentially tell the collar of your
function through your function signature
what they should expect each value to
represent let's talk about one of my
favorite programming patterns or
programming Styles um that is early
returns or um what they're also
sometimes called as guard Clauses so an
early return or a guard Clause is
exactly what it sounds like it's just
when we return early from a function so
this function divide if it's past a
divisor of zero then it returns early
with an error other wise it goes ahead
and does kind of the division and
returns uh the results and a nil error
now we're going to talk about errors
soon you don't have to worry too much
about how they work for now um just
understand that a nil error effectively
means no error so when we're looking at
this divide function we can understand
that if the divisor is zero we're going
to return early and and basically say we
can't do this division because we can't
divide by zero um otherwise we'll take
the happy path towards the end of the
function best practices when it comes to
software engineering and how we write
code change all the time right there's
millions of developers all around the
world writing code and we all have
different opinions and kind of the the
common opinion about a certain style
tends to change over time um the
interesting thing is I think that these
days um guard Clauses and early returns
are kind of looked at as a good thing um
this is clean code right this is a good
way to write code certainly most go
programmers think this way way um but it
wasn't always that way um there used to
kind of be a heuristic that uh
developers used which was you shouldn't
ever return from a function in more than
one place so back when that was kind of
the more popular way of doing things
you'd get kind of nasty if else nested
statements like this um if you look at
this function get insurance amount
basically takes a status as input it
returns an integer and it only has one
return statement so it only returns from
one place but I would argue that doesn't
necessarily make this function all that
much easier to understand right we're
initializing a variable up at the top
amount and then just in this big nasty
nested if else chain we're kind of
reassigning the value of amount uh based
on some conditional logic now compare
that with guard Clauses right so with
guard Clauses instead of overwriting the
variable amount and then returning it at
the end of the function we're just
returning early with the proper amount
at each step of the way now both of
these functions do the exact same thing
they have the same behavior um but I
would argue that the one with the guard
Clauses is much easier to understand so
the question for this exercise is which
is true guard Clauses are unreadable
guard Clauses are generally worse than
nested IFL statements or guard Clauses
provide a linear approach to logic trees
okay so it's definitely not um these two
right so I'm I'm going to go with
provide a linear approach to logic trees
and really all that means is rather than
having to follow kind of a tree
structure to look at conditional logic
we can just follow a straight line right
is it this no we move on is it this no
we can move on right it it allows us to
break up kind of the cognitive load when
we're when we're reading code so
definitely a linear approach there the
next question is what is a guard Clause
so a bit wise or operation an and
operation in Boolean logic or an early
return from a function when a given
condition is met and it is an early
return let's talk about structs so
structs are the first collection type
that we're going to talk about in this
course a collection type is just a type
that contains other types in the case of
a struct a struct is just a collection
of key value pairs if you're familiar
with python dictionaries or JavaScript
object literals this is basically the
same idea so for example we can define a
car struct and we can say a car has a
make a model a height and a width and
each of those feels has its own
Associated type so let's move on to the
assignment I think looking at the code
is going to be the easiest way to
understand structs okay complete the
message to send struct definition it
needs two Fields phone number an integer
and message a string and um on these
exercise I always recommend kind of
going and looking at the test weite this
is all the code the way boot Dev works
like all the code is here um and we're
really just testing standard output to
see if you got the right answer so you
can see literally everything that's
going on
um so here you can see where a message
to send is going to be instantiated with
phone numbers and messages and here you
can see where um it's the uh fields are
going to be accessed with the dot
operator so let me go ahead and run it
in its current state um we should get
yep a compile time error where it's
saying message is undefined phone number
is undefined right so we need to add
those to the
definition all right phone
number
integer message
string let's go ahead and run
that and sending message love to have
you aboard to that big number okay that
looks good to me struct keys can hold
any type uh not just primitive types
like integers strings and booleans um
here you can see we've actually nested
the wheel struct within the car struct
right so we have the car struct from the
last example and we've added a front
wheel and a back wheel and they are each
of type wheel right and a wheel has a
radius and a material so we can actually
Nest structs within other structs and
then we also saw this just a little bit
in the last assignment but this is how
we can instantiate a new instance of a
struct right so this is the struct
definition we're saying this is what a
car looks like and then here we've
created a new empty car called my car
and when you create it kind of with
those empty uh those empty brackets all
of the fields inside of the struct will
just be initialized to their default
values their zero values right so
strings will be empty strings ins will
be zero and then here we're using the
dot operator to access Fields right so
my car. front wheeel right so we're
accessing the front wheel key and then
radius to access access the radius
within the front wheel and we're setting
it equal to five again with this syntax
stuff it's just best to get Hands-On
keyboard and jump right into it so let's
get to the assignment says textio has a
bug we've been sending texts with
information missing before we send text
messages in texo we should check to make
sure the required Fields have nonzero
values notice that both the user struct
so that's this here is a nested struct
or notice that the user struct is a
nested struct within message to send
okay so message to send has a message
which is a string and then a sender and
a recipient and both of those are of
type user okay that makes sense a user
is the sender a user is the recipient
and then there is a message cool
complete the send the can send message
function
okay it should only return true if the
sender and recipient Fields each contain
a name and a number if any of the
default zero values are present return
false instead Okay cool so can send
message this is essentially a function
that's going to validate a message to
send to see if it actually has data
inside of it so so um if I run the code
right now it's always returning true
right so you have an appointment
tomorrow you have an event
tomorrow from Susie Saul ah see there's
a phone number missing there that's a
problem looks like there's a phone
number missing there that's a problem
okay let's see so sender and recipients
contain a name and a number so if M to
send
dot sender and recipi sender contain
name dot name
is
empty
false
right and then if m dot is going to be
recipient is empty also return FSE so
we're just going to do some guard
Clauses
here and then sender and recipient and
now we're interested in the number
number is an integer so if it's
zero cool so we're just we're just
basically doing a a very simple
validation to make sure um that names
aren't blank and numbers are not blank
so let me run
that okay so this one has all the
information there and it's sent now this
one has a number missing can't send
message that looks
correct okay this is looking good to
me all right next up we have Anonymous
strs so Anonymous strs are just struct
instances that don't have a name so
whenever you create a new Anonymous
struct in go you're you're immediately
instantiating a struct of a given type
right the type of the struct doesn't
have a name so for example here we have
a
struct with a make and a model now in
the exercise previous we remember we
actually had this same exact struct but
we'd given it name the name was car
right here we haven't created a new
struct definition we haven't created a
new struct definition called car instead
we're immediately instantiating a new
instance of a struct called my car this
could be named anything right and it
just has a make and a model field on it
so the only reason you would use an
anonymous struct is if you have no
reason to create more than one instance
of the struct so to be clear what's
happening here we're creating a new
variable called my car and it has two
Fields it's a struct with two Fields
make and model and we're immediately
giving it a value of make Tesla Model 3
and this type this specific struct type
doesn't exist anywhere else within our
program this is kind of a type that's
just unique to this one instance um
called my car it's not very common that
you'll see kind of top level um
Anonymous structs like this more often
you'll see nested Anonymous structs
right so rather than creating wheel as a
separate struct type we've just said
well Wheels kind of always exist within
cars I know that's not really true but
maybe within our program it's true um so
we just create a little Anonymous struct
um within the greater car struct now as
far as best practices in writing clean
code my opinion is that you should
generally favor um named structs you
avoid Anonymous structs unless you have
a really good reason to use them uh
you'll really never go wrong with naming
your structs okay so the question on
this assignment is what is a good reason
to use an anonymous struct you're
worried about security you need your
code to be faster you're worried about
user privacy or it is only being used
once well the only thing even remotely
related to how Anonymous trucks work um
is that it's only being used once so if
you're certain that you only want this
type to be used one time maybe you don't
want someone accidentally reusing a type
um then you might want to use an
anonymous
struct next question is what's one
advantage of using an anonymous struct
anonymous trucks make your code run
faster Anonymous strs prevent you from
reusing a struct definition you never
intended to reuse or Anonymous TRS can
be compiled more quickly um it's this
reuse one um one place that I have used
Anonymous kind of top level structs from
time to time is in HTTP handlers so if I
know that a given HTTP endpoint will
always return a a specific Json payload
then I'll use an anonymous struct to
define the shape of that Json payload we
haven't really talked about Json in go
yet but kind of spoiler alert structs
are how we structure Json data
typically next we have embedded structs
and embedded structs are not the same
thing as nested structs an embedded
struct is basically where would take all
the fields from one struct and kind of
shove them into another one uh let me
show you what I mean so here's our car
struct that we've been using for our
examples um it's got to make in a model
great here's our truck struct now you'll
notice we've embedded the car type here
but the car is missing kind of a name a
key in the truck struct bed size is the
key int is the type here we just have
the type which is car so what does this
do and how does it differ from a nested
struct well in the embedded struct if we
want to access the field model from a an
instance of a truck rather than doing
truck. car. model we would just do
truck. model because these fields of the
car type are becoming kind of top level
fields of the truck type we're
inheriting all of those fields from the
car type now I have to be careful with
the word inherit even though it is kind
of a pretty good descriptive term for
what's happening go is not an
objectoriented language because it
doesn't support classes or inheritance
in the class-based sense so if you're
familiar with the idea of
object-oriented programming just know
that classes and inheritance aren't
really what's going on here uh this is
you can almost just think of this as a
shorthand for kind of retyping this make
and model into the truck struct it's
it's almost just a syntactic sugar um so
that we don't have to retype all of
these Fields so let's take a look at
some code um and how we would use this
truck struct so I've created this new um
instance of a truck called it Lane's
truck has a bed size which is an integer
right and it has a car here now you
might look at that and say that looks an
awful lot like a nested struct and the
Syntax for creating a new instance of an
embedded struct is very similar to uh
the Syntax for a nested struct
essentially the key
is just the same name as the type this
just kind of a quirky thing about um
composite literals uh the embedded stuff
looks like the nested stuff however when
we are accessing the individual fields
on um Lanes truck using the dot operator
you'll see they're all accessed at the
top level it's not Lan truck uh doar
doake it's Lan truck. make right it's
not lanest truck. car. model it's just
LAN truck. model so those fields are
being brought up into the top level it's
just when we kind of instantiate the
truck the first time that we need to do
this sort of nested syntax so let's hop
into the assignment it says at texo a
user which is a struct represents an
account holder and a sender is just a
user with some additional sender
specific data a sender is a user that
has a rate limit field that tells us how
many messages they are allowed to send
fix the system by using an embedded
struct as expected by the Tex test code
okay so let's go ahead and take a look
at the test
code so it looks like the test code is
creating some
senders and it's expecting that a sender
has a rate limit and that it has a user
right and this is looking like an
embedded
struct and up here you can see s is a
sender and we're directly accessing name
number and rate limit all the top
level so I think all I need to do here
is embed the user structure in fact
let's run this without without that see
what happens yep we're getting some
undefined
Fields we'll embed the user type in
there and that's looking pretty good to
me I will mention just to give you an
idea of like when you use this in the
real world one place that I use it
actively on boot Dev is users have kind
of public fields on boot Dev and private
Fields so public fields are um stuff
that we show on the leaderboard things
that kind of anyone can see about your
profile maybe your bio or your profile
picture um but users also have private
Fields things like um your hashed
password right the password we store in
the database and I've actually
embedded the private Fields within the
public user so that I can easily nullify
them when I don't want to send private
data uh to a given web page all right
let's go ahead and run this code
let's talk about methods on structs or
just methods in general in go um I know
I told you go is not object oriented and
it's not but if you squint really hard
structs in go kind of look like classes
in a language like Java JavaScript or
python so methods in go are just
Behavior or functions that we can Define
on a type and more often than not we end
up defining methods on structs although
we could Define methods on any type so
let's take a look at the Syntax for this
so here we have a simple rectangle
struct it has a width and a height right
and here we've defined an area method on
the rectangle struct so this is just a
function right we're familiar with
functions already the only difference is
that we've added this special parameter
before the name of the function which is
again just a parameter that comes into
the function just it's just a special
parameter um and in this case it is of
type rect which is just a struct and
we've named it R and then this function
just returns r. width * r. height right
so it Returns the area of the rectangle
so why would we use a method on a struct
well there are reasons that we'll get to
later when we talk about interfaces um
but for now it's mostly a syntactic
thing a syntactic sugar thing if we have
behavior that we want to Define on a
given type then strs can be a really
good choice they give us this nice
tactic sugar right we create this new
rectangle called R and now we can just
call r. area to get the area of the
rectangle it's kind of a nice way to do
computed properties on a type right so
we could have stored area as a separate
number within the wct struct the problem
with that is now we lose kind of a
single source of Truth when it comes to
the area right if we store the area as
say 50 and the width and height as 5 and
10 but then we update the height and
forget to update the area right now we
have a bug in our code so this is a
great way to kind of have a oneline
accessor to get the area of a rectangle
but we don't have to actually store that
area in our struct as kind of duplicate
data so let's get down to the assignment
says let's clean up texo authentication
logic we store our users authentication
data inside an authentication info
struct okay so that's here it's got a
username and a password we need a method
that can take that data and return a
basic authorization string the format of
the string should be authorization basic
username colon password right so this is
the kind of standard basic authorization
that's used in HTTP requests the
assignment says create a method on
authentication info called get basic off
that Returns the formatted string Okay
cool so let's create a
new a new method and we can kind of
reference this syntax over here so it's
funk and we want the receiver to be an
authentication info struct so I'll just
call it AI oh that's that's actually
confusing let's do off aui
authentication
info and we wanted the name of the
method to be called get basic
off and it returns a
string okay and then we want to return
this format
here so if you remember we can use the
format package to do that so we'll
return fmts
printf remember s printf Returns the
string rather than printing it to
standard out or to the console and we'll
use that
template username and password are both
just strings so we'll use percent s for
our formatting verbs and then we can do
off I dot username first
and ai.
password next okay cool that looks
correct to me let's run
it perfect let's talk about interfaces
so an interface in go is just a
collection of method signatures for
example take a look at this shape
interface so we have an interface it's
named shape and it specifies two
different method signatures so area is a
method that takes no parameters and
returns a float 64 perimeter is another
method that takes no parameters and
returns a float 64 now any type that
implements both of these methods and and
matches their method signatures will
implement the shape interface which
really just means that we can think of
it and treat it as a shape so for
example let's take a look at this
rectangle struct so erect has a width
and a height uh both of which are float
64s and again this is just a this is
just a kind of standard struct and it
has two methods on it uh one is the area
method that takes no parameters returns
a float 64 um one is the perimeter that
takes no parameters and returns a float
64 and because a rectangle implements
both of these methods we can think of a
rectangle as a shape a shape is just
anything where we can kind of calculate
its area and calculate its perimeter and
multiple types can implement the same
interface
so for example this circle struct it has
different underlying data right rather
than a width and a height we can
represent a circle with just a radius um
but to calculate its area and its
perimeter the calculation is a little
bit different right we're using pi for
example um but the method signature is
identical right we don't pass anything
in because we have all the data we need
on the circle struct and we just return
a float 64 so both circles and
rectangles because they implement the
required methods can be thought of as
shapes or we could say they implement
the shape interface let's get into the
assignment I think it'll all start to
make a little more sense so the
assignment says the birthday message and
sending report strs have already
implemented the get message method so
let's take a look at that so uh birthday
message is this struct here sending
report is this struct here they both
have this get message method that
returns a string and they're just it
looks like the strings that they return
are just a little bit different right
the birthday messages get message uh
function returns this like hi blank it
is your birthday on blank and a sending
report says your blank report is ready
right so they just they just format a
little little
differently okay so assignment says
first add the get message method as a
requirement on the method interface okay
so we need to finish the message
interface and add a get message
method that returns a string
Okay cool so now this message interface
because birthday message and sending
report both implement this method we can
think of both of those as messages next
it says complete the send message
function it should print a messages
message which it obtains through the
interface meth method Okay cool so the a
message is an interface so inside of the
send message function we don't actually
know if we're dealing with say a
birthday message or a sending report we
just know that we have access to a
message so really the only thing we can
do with it is call get message which we
know will return a string and it says it
should print a message so we'll do ft.
print line just print out the message
cool okay now this is powerful right
let's go take a look at how this code is
actually called so we have this test
function that also just takes a method a
message and it
sends that message right it's just
calling our send message function
here but down here and this is where
it's most interesting the test function
is not past like interface literals
that's not even like a real thing right
an interface is like abstract type that
represents other types instead because
the test function takes an interface we
can pass into it any struct that
implements that interface so for example
here on line 42 we're passing in a
sending report and then on line 46 we're
passing in a birthday message those are
two different types in a strongly typed
language being passed in as the first
parameter to a single function but the
reason it works is because we're using
interfaces okay let's go ahead and run
this see what
happens your first report report is
ready you've sent 10 messages hi Dondo
it is your
birthday this looks great this looks
great to me
in go interfaces are implemented
implicitly and what that means is when
we have a type like in our last example
we had the rectangle type that
implemented the shape interface we never
had to explicitly write anywhere on the
rectangle struct that it was intended to
implement the shape interface because it
satisfied all the requirements of the
shape interface it just kind of
automatically implemented it and that's
fairly unique to go in a language like
Java we might have to write something
like this um we could take a look at
this little example here we've got this
employee interface and a contractor
struct if we wanted the contractor to
implement the employee we would need it
to still fulfill the interface right by
implementing all the methods but we
might need to also explicitly type
something like
implements
employee right we explicitly say that we
intend to implement that interface in go
it's done automatically let's hop into
the ass assignment it says at texo we
have full-time employees and contract
employees we've been tasked with making
a more General employee interface so
that dealing with different employee
types is a little simpler add the
missing get salary method to the
contractor type so that it fulfills the
employee interface Okay cool so we have
the employee interface we have the
contractor
struct um and if we look at the
full-time struct it looks like it
already
implements the employee interface so we
just need to add the Missing Method
because right now a contractor has a get
name but does not have a get salary Okay
cool so let's go ahead and do that we'll
do
employee get salary it returns an
INT and a contractor's salary is their
hourly pay multiplied by how many hours
they work per year okay so be something
like
c. hourly pay multiplied by c. hours
per
year okay and just because I'm curious I
want to go look at how a full-time
salary uh employee works yeah so you can
see the the way that a full-time
employee their salary is calculated is
is totally different like their their
salary is actually just stored probably
because the way we think of full-time
employees is like you know you make
$60,000 a year you make $70,000 a year
whereas contractors often get paid by
the hour so it make sense that they have
a different calculation um and then down
here in the test Suite um this looks
very similar to the last assignment
where the test function is able to take
as inputs any type of employee right so
we can pass in both full-time and
contractors here uh let's go ahead and
run
that Jack Bob and Jill we can see all of
their salaries even
though Jack is full-time and Bob and
Jill are both contractors so that looks
good to me in go types Implement
interfaces implicitly because it kind of
decouples the definition of the
interface from the definition of the
type the type doesn't even really need
to know that it implements a certain
interface and that's really really cool
because it means it's easy for a type to
inter to implement lots of different
interfaces so the quiz question here is
how is an interface fulfilled answers
are type has all the required interfaces
methods defined on it or a struct embeds
the interface in its definition and the
answer is going to be it has all the
methods defined the next question is can
a Type fulfill multiple interfaces or
Implement multiple interfaces and yes
why not another quiz question go uses
the blank keyword to show that a type
implements an interface and the answers
are fulfills implements inherits and
there is no keyword in go and the answer
is that there there is no keyword in go
interfaces are implemented
implicitly in the next question it says
in the example given the blank type
implements the blank interface let's
take a look so example here we've got
the shape interface circle
struct circle has an area method so it
looks like the circle type implements
the shape interface like we talked about
before or a type can Implement multiple
interfaces it just needs to have all the
methods for all of the different
interfaces for example the empty
interface so that's this this definition
right here this is an interface with no
methods required is actually always
implemented by every single type in the
go programming language now it's often
not a very useful interface because you
can't really do anything with an empty
interface you have no methods to call
let's jump down into the assignment it
says add the required methods the email
typee implements both the expense and
printer interfaces Okay cool so we got
these expense and printer interfaces two
different methods we need to implement
and we have this email type with an is
subscribed Boolean and body string cool
all right cost
method if the email is not subscribed so
if not e do is subscribed I believe is
what the field was then the cost is
0.05 times
uh the length for each character in the
body so the length e do body now
remember go is strongly typed and we
can't multiply an INT by a float
directly so we need to cast this integer
sorry I think I highlighted those
backwards the length of the body is an
integer this is a float so we need to
cast the length of the body to a float
so we can we can multiply a float by a
float um otherwise we'll
turn uh it's
0.01 times that same thing the length of
the body cool uh the print function
should just print to standard out the
emails body text easy enough fm.
printline e.
body okay so now our email struct
implements both of these interfaces and
if we come down here to look at the test
function this is interesting the test
function takes an expense and a
printer right so we can use both of
those uh methods
there and actually the email struct so
we're creating instances of emails here
we're actually passing it into test as
both the expense and the printer right
because it makes sense it implements
both cool let me run
that oh what did I screw up 05 time FL 6
4 length value of type float 64 is not
used let's
see 0.05 time float 64 length of e body
what did I screw up value of type float
64 is not
used I forgot my return cool let's try
that
again okay
so printing with cost 11 cents hello
there printing with cost $1 I want my
money back yeah that looks that looks
pretty good let's submit
it up until this point we haven't really
been naming the arguments of our
interfaces method signatures um but we
totally could so if we take a look at
this this is The copier interface it's a
uh copy method that it requires which
takes two strings and returns an INT um
the interesting thing is when you look
at this method signature it's kind of
hard to tell what the intention behind
the interface is it's like great it
takes two strings but what are those
strings supposed to represent right so
here we can take a look at what I would
consider a better interface definition
where The copier interface actually has
a copy method that specifies the source
file is kind of the name of the first
parameter and the destination file is
the name of the second parameter and
then byes copied is the integer that's
returned so you know the functionality
here is identical but now we have much
better I I mean I would consider it
documentation of what the intention
behind this interface is so the question
is are you required to name the
arguments of an interface in order for
your code to compile properly uh no no
it'll work fine either way next question
is why would you name your interfaces
methods parameters right like we don't
need to why would we do it um execution
speed memory savings or readability and
Clarity uh it's going to be for
readability and Clarity type assertions
so type assertions are something you'll
see every once in a while I would argue
they're probably not super common but
you will come across them um and the
whole purpose of a type assertion is so
that you can take an interface and kind
of cast it back into its underlying type
so in this code sample here we can kind
of assume that s is an instance of a
shape interface and at this point in the
code because it's just an interface we
don't know NE neily if it's a circle or
maybe a square or maybe some other type
so what we do is we do a type assertion
to cast it to the circle struct and
basically what happens here is on the
left hand side of the um this uh short
declaration operator here we get back
the instance of the circle so this is
going to be an instance of the circle
struct again the underlying struct um
behind the shape interface if it was a
circle right because we we can't be sure
if we had a shape we're not sure if it
was a circle or maybe something else but
this okay variable this is going to be a
Boolean and if it's true then it was a
circle and we should have a valid kind
of filled out Circle struct if okay is
false then we kind of have to discard
the circle um because we we weren't able
to parse out um the shape as a circle
because it wasn't a circle so let's move
on to the assign it's an email then it
should return the email's two address
and the cost of the email if the expense
is an SMS then it should return the
sms's phone number and cost if the
expense is any other underlying type
just return an empty string and a 0.0
for the cost Okay cool so we've got this
get expense report that takes an expense
as input and my guess is yep expense is
this interface so our job is to kind of
try to cast it into the potential
underlying types email and SMS
so first we do um email
okay uh expense Dot and we'll cast it to
an email type if
okay so if it's an email return the
emails to address so uh return email. to
address as that string there and also so
it's cost so email dot looks like cost
is a method email. cost
okay uh next we can do s
okay uh e do cast to an SMS so if it's
an SMS we can
return sms's to phone number to phone
number and the SMS Dot cost I believe
that's also just yep just a
method okay and it says otherwise if it
has a different underlying type return
empty
string and 0.0 for the cost okay cool
that
feels at least like we followed the
instructions let's see what we
get not enough arguments in call to sms.
cost let's take a look at sms. cost
cost
H have
want what did I screw up
here ah should be s. cost I I named I
named this truck the struct S all right
let's try that
again report the email going to John do
will cost 11 email the sms okay cool and
invalid expense perfect that I mean that
feels right I can come down here and
check the test Suite yeah invalid okay
cool let's submit that when we want to
do a lot of successive type assertions
in a row there's actually a better way
and that's with type switches so here's
a Syntax for a type switch uh basically
we have an interface and we cast it um
at the very top of the switch and then
we can actually check and kind of
pattern match against different possible
types if none of the types that we've
specified are the matching underlying
type then it will kind of fall down to
the default case so let's write some
code and see how it works um down in the
assignment it says after submitting our
last snippet of code for review a more
experienced gopher or term for a go
developer uh told us to use a type
switch instead of successive assertions
let's make that Improvement implement
the get expense report function using a
type switch Okay cool so kind of the
same thing though if it's an email
return a to address and a cost if it's
an SMS then we should return a phone
number and a cost blah blah blah right
the difference is we're going to use a
different syntax this time okay so we're
going to be using this switch syntax so
switch value colon equals e.type
okay Open brackets case um we are
interested in if the expense is an
email so case email now the the
interesting thing is with now within
this case block V is an email so I can
treat it like an email so I can do
return V
Dot oh what was it two address I think
yeah two
address b.
cost a
SMS return B do two phone number I think
it was two phone number b.
cost and
default
return uh I think it was empty string
just zero values right empty string and
0.0 okay cool let's see what
happens with that oh am I forgetting
nope I've
got oh I'm indenting a little
weird there we go let's run
that cool the MS SMS is going to be is
going to this number with this
cost got some emails and costs okay
that's looking good to me now that we've
learned what interfaces are and how we
use them let's talk about how we can use
them more effective and in a sort of
idiomatic and clean way if you forget
all of the other advice that we're going
to go over when it comes to writing
clean interfaces don't forget this one
and that is to keep interfaces as small
as you can the best the cleanest and the
most useful interfaces typically just
have one or two methods defined on them
imagine a simple Stringer interface it
has one method defined on it called
string that simply returns a string you
can take that interface and implement it
on basically any type and now you have a
super useful interface for logging out
string representations of different
types there isn't a hard and fast rule
about exactly how many methods you
should be defining on your interfaces
but what you really should be doing is
looking for kind of the minimal Behavior
necessary to accurately represent an
idea or concept right so for example
here's a slightly larger interface from
the standard Library again normally
we'll see interfaces in the standard
library with maybe just one or two or
three uh methods this one has five but
this is kind of still the minimum um
amount or or the the the minimal
necessary behavior that we need to
describe a file like a file in your file
system right we need a way to close the
file we need a way to read it we need a
way to seek to individual uh kind of
sections of the file um so this is an
example of a slightly larger interface
right I would say five methods is
definitely a on the larger end um but
it's still a good interface because it's
using as as little Behavior as it
possibly can to describe an operating
system file so the question that goes
along with this is interfaces should
have as blank methods as possible and
the answers are complex few and many and
the answer is going to be few the next
mistake that I've seen new go developers
make is when they write an interface
that sort of knows about the types that
they've intended to satisfy it so for
example if you have a car interface and
you've defined an is fire truck method
on the car interface that returns a buou
you know whether or not it's a fire
truck that's probably a mistake you
would actually just want to use a type
assertion or a type switch if you really
needed to figure out the underlying type
you should not make your interfaces
aware of the underlying types this also
breaks rule number one right because
rule number one we don't need to know if
a car is a firetr for like the minimal
behavior of a car I mean you can see how
this sort of design pattern would get
out of hand very quickly because we
might need to also Define other booleans
like is pickup is sedan is tank right if
you start catering to all of the
underlying types of a given interface
your interface is going to be really
bloated and become very very large so
the question here is actually the
reverse it says it's okay for types to
be aware of the interfaces that they
satisfy if it were flipped around the
answer would definitely be false but
because we're talking about the types
being aware of the interfaces they
satisfy I don't think that's necessarily
a problem um they don't need to be aware
of them right because again in go uh
interfaces are satisfied implicitly but
I would argue to some extent they are
aware of them because the developer had
to go in some cases out of their way to
satisfy the implementation of an
interface so I'm going to go with true
on this one so now this is the reverse
question it says it's okay for
interfaces to be aware of the types that
satisfy them no they should not be aware
finally I just want to talk about how
interfaces are not classes sometimes
this can get mixed up especially if
you're coming from an object-oriented
background um inter Es are just a very
different idea interfaces are not
classes they are much Slimmer right
classes have a lot of functionality
going on inheritance is a fairly complex
topic um interfaces are honestly just a
simpler idea um interfaces don't have
Constructors or deconstructors that
require that data is created or
destroyed right so that's another way
that they differ from classes that have
kind of this in inherent setup and tear
down uh situation interfaces are not
hierarchical right there's there's no uh
hierarchical inheritance tree when it
comes to interfaces and the most
interesting one is that interfaces
Define functions signatures but not
underlying Behavior right so this is
actually a big difference between
interfaces in classes in a class A Child
class can inherit behavior from a parent
class which can kind of dry up your code
right don't repeat yourself in go that's
not the case at all an interface doesn't
dry up your code you still have to go
Implement all of the methods on all of
the different types individually
interfaces just allow us to use all of
those types kind of in the same places
later in this course we'll get to talk
about generics which is a way to kind of
dry up your code and funny enough kind
of uses interfaces um under the hood or
kind of as a part of the generic system
to make that happen so finally the
question is interfaces allow you to
define a method's Behavior once and use
it for many different types uh that's
false go has a very unique way of
handling errors really quickly let's
review how JavaScript handles errors and
the way JavaScript handles errors is
very similar to python or Java it uses a
TR catch Paradigm um and let's contrast
that with how go handles errors in its
sort of unique way let's pretend that we
have a function called get user um and
we'll start with JavaScript so we have a
function called get user
whoa and it Returns the user so we'll do
const user equals get user and we have
to wrap the get user function the call
to the get user function and a tri block
because we know that the get user
function can throw because let's just
say for example uh maybe the user
doesn't exist Okay cool so if something
goes wrong we add a catch block and if
this function throws execution will stop
we'll enter the catch block and do
whatever is in here for now we can just
uh console.
log the error now let's take a look at
what this function looks like in go we
do something like this we do user air
colon equals get
user if Air does not equal
nil then we could print out the error
and return so that we don't continue and
then down here we could you know deal
with the user object so uh use user here
and for consistency sake we could say
use user here um after the user object
is created in JavaScript okay so what's
the difference well let's talk about the
first reason why I prefer goes error
handling to javascript's error handling
let's pretend we need to go get some
data for the user after we've already
kind of retrieved the user successfully
so let's say we need to go get the
user's profile picture well in go all we
would do
is call the next dangerous function
let's say get user Pro profile and maybe
it takes a user. ID as input right so we
need to successfully wait for the user
to come back and then we can go fetch
its
profile right so this looks very similar
we doing another dangerous function get
user profile and it returns a profile
and an error if an error with the
profile occurs uh again we'll just kind
of print out the error and return
otherwise now we have a profile object
okay what would we do in JavaScript well
in JavaScript we can't just take this
Tri catch and paste it down here and
update some stuff this will not work if
we do get user profile
user.
ID
profile the problem with this code is
that user is only available within this
Tri Block it's scoped to the tri block
right so this user object here will be
undefined so the normal thing to do in
JavaScript would just be to take this
line here and inject it
within the tri block the original Tri
block right so now I'm doing my second
dangerous function after I do my first
dangerous function the reason I don't
like this is that the logic for handling
the errors is now all in one place if I
want want to handle the error for the
get user profile function differently or
separately um then I want to handle the
error from the get user function I have
to actually Nest the whole Tri
catch like
this right so now user. ID is accessible
because I'm in the same Tri block but I
get a second catch block so we kind of
get this nasty nesting if we want to
treat each individual error separately
and this kind of brings us to the second
reason why I prefer Go's error handling
and it's that it forces us to think
about each individual error that's
passed back from a dangerous function so
again in JavaScript kind of the normal
thing to do unless you really needed
separate error handling would be to just
kind of do all of your dangerous stuff
in one large Tri block the problem with
this in my opinion is that it kind of it
doesn't encourage me to remember which
of these functions is dangerous in the
first place for example maybe this is a
safe function and this is a dangerous
function that can potentially throw an
error but looking at this code from um
kind of a calling perspective I don't
really know that that's the case whereas
in go because a function returns kind of
the valid data and the error with every
function call I know for a fact that the
get user profile function can throw an
error because it returns an error value
this will probably make more sense if I
actually write out some of these
functions so in JavaScript the get user
function might look just like this
function get
user uh we could say uh do some get user
logic
here and then maybe it returns you know
a
user looking at the function
signature all I can tell in JavaScript
is that this function takes no arguments
and the only way for me to know what it
returns is to go find that return
statement see oh okay it returns a user
however in go I actually get two super
helpful things in the function signature
and remember the function signature is
just kind of that first line of a
function definition so in go if I were
to write the function definition for the
get user function would be something
like this
funk get user doesn't take any inputs
but it returns a let's say a user struct
and an error right and then we have some
kind of do get user logic
here right so now just by looking at
this function signature I can tell that
it returns a user and that it could
possibly return an error as well that I
need to go handle whereas again in
JavaScript not only do I not get to see
in the function signature what is
potentially returned but I also don't
know that this function can throw I have
to kind of go digging deeper into the
function definition to see if there's
potentially something uh dangerous going
on in the meat of the function so again
to reiterate because this is actually a
super important point when you're
learning about how errors are handled in
go the primary reason I like error
handling in go is that it forces me as I
write my code to be kind of hyper aware
of every potential error and make sure
that I write code that handles it so now
that we understand what error handling
in go kind of looks like from a high
level let's dive into the detail s
errors in go are just values and
specifically they're just interfaces so
the built-in error interface is an
interface with a single method the error
method and that method returns a string
describing what went wrong so how do we
actually go about handling errors in
code well let's take a look at this
function right here so this is a
function in the standard Library it's
called asky to integer it takes a string
and attempts to convert it into an
integer value and potentially that can
be problematic right because you can
write strings that aren't numbers under
the hood Okay cool so what do we do we
get back an integer and an error and in
essence it's it's it's really simple the
error is either nil or it's not nil if
it is nil that means everything went
fine and nothing went wrong so if the
error is nil it means there is no kind
of string representing what went wrong
because nothing went wrong however if
the error is not nil that means
something did go wrong right so if the
error is not nil in this case then we'll
print an error message and return from
the function right so we're basically
writing guard Clauses that say something
went wrong let's handle this error and
get out of here right enough talk let's
write some code so you can see what I
mean let me expand this so it's a little
easier to see the code okay the
assignment says we offer a product that
allows businesses that use texo to to
send pairs of messages to couples it's
mostly used by flower shops and movie
theaters okay great complete the send
SMS to couple function so that's this
function here it should send two
messages first to the customer and then
to the customer's spouse so use send SMS
to send the message to customer if an
error is encountered return 0.0 and the
error uh do the same for message to
spouse if both messages are sent
successfully return the total cost of
the messages added together okay I think
I understand what's going on so we're
basically going to send both of these
messages one after the other um if any
errors happen we kind of abort early and
return the
error okay so uh send SMS takes a
message as input so first we'll say uh
send SMS and we'll send the message to
the
customer and send SMS returns a float 64
and error so we'll say um I think it's
the cost
return the total cost okay yeah so cost
error send SMS all right if error does
not equal nil so if there was a problem
sending the
SMS uh return 0.0 and the error so
return
0.0 right because this function send SMS
to couple Returns the total cost and an
error so we're saying if we failed to
send the SMS then it costs nothing and
we'll return the error that describes
what went
wrong otherwise we'll do it for the
message to spouse so we kind of are just
going to do this exact same thing so I'm
going to say cost for uh for let's say
customer cost for
spouse and we'll be sending the message
to the
spouse
cool all right now if we get to the
bottom of the function that means
nothing went wrong so we can
return cost for
customer Plus cost for
spouse and nil right because if we get
down here nothing went wrong so we don't
have an error to return okay let's go
ahead and run this code and see what we
get and I want to just scroll down and
take a look at some of these test
cases okay message for customer thanks
for coming into our flower shop message
for spouse we hope you enjoyed your gift
error can't send texts over 25
characters Okay cool so I mean if we
look at the send SMS uh function it
actually throws an error or I shouldn't
say throws because we don't throw in go
right but returns an error value that
says can't send texts over blank
characters so that looks correct to me
um here we've got message to customer
message to spouse total
cost okay this is looking pretty good to
me I'm going to go ahead and submit that
we talked about how error handling in go
is all built around the error interface
in fact let's just review that really
quickly uh this error interface is
really just an interface that wraps a
method returning a string because if you
think about it at the end of the day an
error is just a kind of nullable string
either it's a string representing what
went wrong or saying what went wrong or
it's nothing because nothing went wrong
so being good at handling errors in go
has a lot to do with being good at
formatting strings or formatting useful
error messages so let's review how we
format strings in go most formatting go
is built around kind of these formatting
verbs that are defined in the format
package the fmt package of the standard
Library so for example the S printf
function returns a string where it
interpolates the values passed into the
function after the formatting string
into the formatting string uh string
where where the verbs exist so for
example this first percent V is replaced
with name the second percent V is
replaced with age percent V is the verb
we use for sort of the default format
right if you format an integer using
percent V then it kind of just prints
the integer in string form um but there
are other ways that we can format stuff
for example the percent F verb is used
to format floats and you can actually
specify how many decimal places you want
to show up in your output string by kind
of prefixing the F portion with a point
2 for two decimal places or say A.1 for
a single decimal place so let's jump
right into the coding assignment
assignment says we need better error
logs for our backend developers to help
them debug their code complete the get
SMS error string function so that's this
one right here um it should return a
string with this format Okay cool so I'm
just going to jump right ahead so it
should
return
turn fmts printf right because we're
trying to format a string uh this format
let me expand that a little
bit okay so return a string with that
format uh cost is the cost of the SMS so
I'm going to replace cost here with the
cost formatted to two decimal places so
percent Point 2 F right for two decimal
places and replace recipient with the
stringified representation of the
recipient's phone number which is the
string
here so percent v um it's important to
point out I could also use S here to
format a string but V and S do the same
thing effectively um when you're working
with
strings
and then we need to pass in as
parameters the values that we want to
format or that we want to interpolate
into the string so it's going to be cost
and
recipient going to Dent that because
that is some crazy
formatting okay that looks better all
right SMS that costs per point2 F to be
sent to percent V this looks good to me
all right I'm going to go ahead and run
that and see what we
get ah right can't forget to import the
formatting package let's run
that okay SMS that costs 1.40 to be sent
to the string cannot be
sent awesome this looks correct to me
I'm going to go ahead and submit that
let's talk about building our own cust
error types so remember the error
interface is an interface in fact let me
jump back and show you what it looks
like again so it's just this interface
here and because it's an interface that
means we can build our own types like
this user error struct here that
Implement that interface which means
they can be used as errors and remember
that the error interface just has one
single method that we need to Define
right the error method that returns a
string and as long as we have that then
our type can be used as an error so for
example we could create this user error
type that stores a name and then we can
use it as an error to format an error
message that contains the name of the
user's account who had an error so for
example in this snippet here we have a
send SMS function and if we're not able
to send a message to a user we can
actually just return that user error
struct with the user's name and again
that is an error we can return that
struct as an error type because it
implements the error interface the
caller of this function would then just
treat it like any other error the reason
this is useful is that we can store
structur data within our errors so if we
want to format them a specific way we
have access to sort of dynamic data like
a name let's write some code that uses
this concept so the assignment says our
users are frequently trying to run
custom analytics queries on their
message deliverability metrics right so
we're sending lots of messages we want
to know are these messages getting
delivered they end up writing a bad
query that tries to divide a number by
zero it's become such a problem that we
think it would be best to make a
specific type of error um for dividing
by zero update the code so that the
Divide error type so that's this error
type here or rather this struct here
implements the error interface its error
method should return a string formatted
in the following way cannot divide
dividend by zero okay let's write that
method so it's going to be a function on
the Divide error type so I'm going to
name it de which is
a a divide error and the name of the
function must be error takes no
arguments and returns a string right so
that's that's the function signature we
need to use um to implement the error
interface and then we're going to return
a string and we're going to need to
format the string so we'll use S
printf uh the format package is already
imported and this is our template and
dividend in this case is a float
64
and uh you it says here dividend is the
actual dividend use the percent V verb
so percent V okay so we're just going to
kind of do the default formatting for a
float
64 and
let me indent all this so that it's kind
of
readable where dividend is the actual
dividend of the divider okay so we'll do
de do
Dividend that will interpolate there
okay that looks correct to me I think
we've implemented the error interface
properly let's go ahead and run
that uh oh I forgot my
comma um this is kind of an interesting
Quirk of the go programming language um
if you to add a new line after the last
parameter to a function you have to put
a comma so this would
work all right
and this would
work but this does not work right that's
what I did the first time so just kind
of something to watch out
for okay so let's take a look at this um
dividing 10 by 0 cannot divide 10 by 0
dividing 10 by 2 quotient 5 dividing 15
by 30 5 question okay this is looking
like it formatted properly let's submit
that so we've got a quiz question it
says what is the underlying type of an
error is it an interface is it a struct
is it a string well it could technically
be a struct or a string right like we
saw in the last uh the last assignment
but the most correct thing would be to
say an
interface because it must always be an
interface right it must always implement
the error interface it could also be a
struct or a string um but it will always
be an interface next question is can a
Type be an error and also fulfill
another interface well errors are just
interfaces and we know that types can
fulfill any number of interfaces as long
as they have all of the required methods
so yeah there's no problem with this
let's talk about the errors package so
the standard library and go has an
errors package that exposes a few useful
functionalities but the one we're most
interested in right now is the errors.
new function so the useful thing about
the errors. new function is allows us to
create a new error from just a string so
we don't need to kind of you know Define
a new struct or a new type and then have
it explicitly implement the error
interface that can be a lot of code if
all we want to do is kind of return an
error with a very specific string let's
go ahead and use it the assignment says
twilio software Architects may have over
complicated the requirements from the
last coding assignment yeah all we
needed was a new generic error message
that returns a string no dividing by
zero when a user attempts to get us to
perform the taboo right the taboo of
dividing something by zero complete the
divide function use the errors. new
function to create an error when y
equals z that reads no dividing by zero
okay so this is pretty straightforward
basically if Y is zero which means that
the the number that we divide by would
be zero which is obviously a huge
problem in mathematics you're not
allowed to do that in go or most
programming languages that I'm aware of
um so we need to return an error here uh
so that this operation never happens so
we'll return errors. new looks like the
errors package is already imported for
us and we just need to return an
error that says no dividing by
zero okay let's go ahead and run that
and see what we
get oh I screwed up this function
returns a float 64 and an error so we
should return a zero value for that
first value when we're returning a
non-nil error let's go ahead and run
that cool dividing 10 by Z no dividing
by
Z all of this looks good to me let's
talk about loops in go so if you are
familiar with Loops in other languages
Loops in go syntactically are very
similar to say Loops in JavaScript uh
the main difference is just that we
don't use the parentheses around uh kind
of the the signature of the for Loop um
which again is very similar to how if
statements in go work we're basically
just dropping those uh parentheses
syntactically um the initial portion
runs at the beginning so for example in
this uh snippet of code where We're
looping over the integers 0 through 9 UM
in the initial section we're just
initializing a variable I and setting it
equal to zero um in the condition
section uh we're checking and making
sure that I is less than 10 so at the
end of every uh kind of iteration of the
body which in this case we're just
printing I in the body um we're going to
check and make sure that I is less than
10 if it is we'll continue on to the
next iteration of the loop otherwise
we'll be done um and then at the end of
every iteration we'll also be running
this after section so we'll be
incrementing I and that happens before
uh the condition is run so for example
if we increment I to 10 and then I is no
longer less than 10 we will not continue
on to the next iteration of loop which
is why this prints 0 through 9 and not 0
through 10 10 let's jump right into the
assignment says at texo we have a
dynamic formula for determining how much
a batch of bulk messages cost to send so
we need to complete the bulk send
function it takes a number of messages
as input and returns a float 64 um which
it looks like will be the total cost of
the batch of messages okay each message
costs 1.0 plus an additional fee the fee
structure is so it's going to be first
message is 1.0 plus 0 so 0 is the fee
second message is 1.0 Plus
01 third message is 1.0 plus 02 okay
cool and then our job is to use a loop
to calculate the total cost of all of
these messages for you know the number
of messages that we've been given okay
let's let's just start writing some code
so total cost I'm going to just start it
out at 0.0 and then we'll use a loop or
I equals 0 uh to do like num messages
iterations so you know a number of
iterations equal to num messages so um
start I at zero uh and then we'll do I
is less
than n
messages and I
Plus+ okay so this uh kind of body of
the for Loop should execute numb
messages times and we're going to want
to add the total cost so plus equals um
and we're going to use this formula so
it's going to be 1.0
plus this fee and how do we calculate
that fee well it looks to me like it is
0.01
times the basically the message number
right so for the first message at index0
I equals 0 um it's going to be a fee of
zero so if we just use I this should
work because if I is zero and we
multiply that by 01 anything multiplied
by 0 is 0 right so we'd get 1.0 + 0 for
the second message I should be one so
we'll get 1 plus 01 because anything
multiplied by one is itself um and so on
and so forth um that's looking correct
to me at the end of the function we'll
just return a total
cost cool let's run that oh untyped
float constant truncated to int okay so
I is an INT and we cannot multiply an
INT by a float 64 so I'm going to cast
um I to a float 64 let's try that
again and take a look at some of these
numbers Okay cool so the cost for 10
messages is
10.45 and and that sounds about right
right because each message costs
one plus this like fractional part that
grows over time so like the 10th message
would have cost like
0.10 and it would decrease over time so
that looks about right to
me 50
messages yep okay I'm going to go ahead
and submit that one interesting thing
about four Loops in go is that each
section of the for Loop the initial the
condition and the after are actually
optional and we can omit any of them so
for example if we omit the condition
then the for Loop will just run forever
so let's see why this might be useful um
let's jump into the assignment the
assignment says complete the max
messages function given a cost threshold
it should calculate the maximum number
of messages that can be sent um and then
it looks like the fees for each message
are going to be identical to the last
assignment so let's go ahead and just
get started um so max messages we have a
threshold a cost threshold right we want
to see how many messages we can send
while keeping the total cost under the
threshold so I'm going to create a total
cost variable and set it equal to
0.0 and then this is going to look
really similar to the last assignment um
but basically we'll set I equal to zero
to
start um I'll skip the condition for now
because we don't know to what number
we're kind of looping up to right we're
trying to calculate that so I don't know
where stop yet um but I do want to
increment I with every uh iteration of
the
loop and then I basically want to um
update the total cost by adding to it um
and use uh this formula over here right
so it's going to be
1.0 plus
0.01
time time I which I need to cast with
64 okay so that formula is looking again
identical last time
now if I want to
return the the maximum messages that I
can send while keeping the cost under
the threshold then basically I need to
check and say if the total cost is
greater than the
threshold then I should return
IES that sound right so for example
let's say that right off the bat the
threshold were I don't know is it
negative one right so I'm not allowed to
say send anything I would want to in
that case return zero right cuz I can't
send any messages so what would happen
we'd enter the loop I'd calculate the
total cost for the first message which
would be
1.0 and then because the total cost is
already higher than the threshold I
would return I which would be zero so
that works right however if the
threshold were a little higher let's say
the threshold were 1.5 uh then what
would happen is we do that comparison
the total cost would be less than the
threshold
so we'd continue to the next Loop uh
iteration of the loop where I would be
incremented to one and then on that next
iteration we'd go over okay so this is
looking good to me let me go ahead and
run this
code okay so with a threshold of 10 I
can send nine messages without going
over the
threshold that sounds right right
because I'd have like nine in some
fractional
part let's go ahead and mcy most
programming languages have support for a
while loop and really a while loop is
very similar to a four Loop except it
doesn't have that kind of initial and
after statement it it just runs until
some condition is no longer true now
because of this similarity the authors
of the go programming language decided
to not include an explicit while loop
with a while keyword instead the for
Loop is a while loop or just basically
both those side um kind of sections are
omitted and we just have a condition
that follows the four keyword so in
other words if we have a four keyword
and then a single expression then it is
a condition um that while true will
continue to kind of run the body of the
loop over and over and over again until
the condition stops being true let's
take a look at an example um here we
have a variable called plant height we
start it uh equal to one and we've done
this outside of the for Loop right and
then within the for Loop we just have
one section where we're comparing the
variable plant height to the number five
right and while it is less than five
we'll print this message and then at the
end of the loop we'll increment plant
height now you might notice this looks
just like a for Loop where we've
essentially taken the initial statement
and moved it up outside of the for Loop
body and we've taken the after statement
and moved it within the for Loop body
and that is what we've done right but
it's to demonstrate that this is valid
syntax let's jump right into the
assignment okay so the assignment says
we have an interesting new cost
structure for our SMS vendor so at texo
we have a vendor that we have to pay to
send text messages through right so
we're a software service that makes
sending text messages easy but we do
have to pay some kind of uh maybe
Hardware service that actually does the
sending of uh the text messages kind of
over the wireless network okay um they
charge exponentially more money for each
consecutive test text we send let's
write a function that can calculate how
many messages we can send in a given
batch uh given a cost multiplier and a
Max cost in pennies okay so given a cost
multiplier and a maximum cost our
function will return basically the
number of messages that we're allowed to
send um that's under that that Max cost
so it says in a nutshell the first
message costs one penny Okay actual cost
and pennies starts at one and each
message after that first message uh
costs the same as the previous message
multip by the cost multiplier okay so
that's happening here um gets expensive
uh there is an infinite Loop in the code
okay so on line 10 here we have this
four with no body and then an open curly
bracket um let me show you what happens
when we run that our codee's going to
sit and run and execute the body of that
for Loop over and over and over and over
with no exit condition uh this is
obviously a problem we don't want uh an
infinite for Loop there so our job is to
exit before incrementing Max messages to
send if the cost of the next message
would go over the Max cost so all we
need to do is check and only execute
this Loop while the actual cost is less
than or equal to the Max cost in
pennies um and we're going to have to
cast looks like so this is a float this
is an inch so we're going to cast this
to
64 now this should work because Max
message to send starts at
zero so assuming the actual cost is less
than the Max cost then we go ahead and
increment say we can send one more
message and then we take a look at the
next cost and if the next cost is still
less then we'll essentially get to add
another message so we'll keep kind of
looking ahead and calculating the next
cost right and as soon as the next cost
goes too high we stop okay so I'm going
to go ahead and run
that cool so with a multiplier of
1.1 and a Max cost of five we can send
17 messages that sounds about
right right the actual cost starts at
one the Max cost is five multiplier of
1.1 yeah that sounds right um so the
multiplier goes up Max cost goes up now
we can send nine messages that all looks
good to me let's talk about the modulo
operator how it works and go and kind of
how it works uh generally so the modulo
operator is a percent sign so it looks
like this right and the modulo operator
essentially calculates
remainders remainders so what do I mean
by that well let's jump into an example
uh let me switch colors so
four 4 / 3 in sort of a normal uh
floating Point division uh equals like
1.33333 forever right um we have this
fractional part however that's how math
Works normally and that's how math Works
in floating Point division in a language
like go if we're doing integer division
then we can't have a floating Point
result so the result of four divided 3
and integer division is actually just
one it's essentially the number of times
that three can be divided evenly into
four and then we chop off the remainder
the interesting thing about the modulo
operator is that 4 modulo 3 doesn't
return the number of times that three
can be divided evenly into four it
Returns the
remainder after that division so
actually in the case of Four modulo 3
the remainder is also one so it's the
same but for example five modulo 3
is
two and 6 modulo
3 is zero again because three divides
evenly into six right six integer
division by three is two right because
three goes evenly into six twice but the
remainder is zero that's why 6 mod 3 is
zero let's just do a couple more
examples feel free to pause the video um
in between when I give the question and
provide the solution um so that maybe
you can practice a little bit okay so
let's do
12 mod
4 we'll do in fact maybe I'll just write
out all the problems first let's do
16 mod
5 let's
do
22 mod
8 and let's let's do
27
mod
6 I think those will be those will be
good okay 12 mod four so does four
divide evenly is 12 uh yes 4 * 3 is 12
right so the remainder is
zero all right how many times does five
divide evenly into 16 the answer is
three right 5 10
15 and the remainder would then be one
how many times does 8 go into 22 8 16 24
so 24 doesn't work so it's going to be
16 and then 22 subtract 16 is 6 is the
remainder right and then 27 mod 6 um 6
goes into 27 four times 6 * 4 is 24 so a
remainder of three so an important thing
to note here is if you're trying to
figure out if a number divides evenly
into another number then you can just
check if say a mod b
equals 0 right if aod B equals z then
that means B divides into a an even
number of times so a is a multiple of B
so in go the module operator is just
that percent sign right so seven mod 3
um this expression is going to evaluate
to one we'll also need to know about the
logical and operator and the logical or
operator which are double Ampersand and
double bar uh respective The Logical and
operator operates on two Boolean values
and only returns true if both sides are
true right this and that the or Operator
just needs at least one side to be true
right in order for the entire expression
to evaluate to True let's jump into the
assignment it says we're hiring
engineers at texo so it's time to brush
up on the classic fizzbuzz game coding
exercises have been dramatically
overused in coding interviews around the
world complete the fizzbuzz function
that print the numbers 1 to 100
inclusive each on their own line but
substitutes multiples of three for the
text Fizz multiples of five for Buzz and
multiples of three and five for fizzbuzz
okay so we need a for Loop for I colon
equals 0 I is less than 100 actually
less than or equal to because it said
inclusive right
i++ okay um we need to think about the
order in which kind of these things can
happen happen so we're checking for
multiples of three multiples of five and
multiples of three and five so we
actually should check for multiples of
three and five first because something
could be that and one of the other two
conditions right it could be a multiple
of three and five and just a multiple of
three that makes sense maybe it'll make
sense when I type it out so if I
mod 3
Z and I mod
mod
three zero oops not three and three
three and five then we'll
print bizbuzz
right else
if I mod 3
0 then we'll just
is else if I imod five is zero we'll
just print
Buzz
otherwise we'll
I
right if they're both multiples Fizz
buzz if it's just three Fizz if it's
just five Buzz okay let's try
that oh cannot forget to import the
formatting package
okay let's
see fizzbuzz one two Fizz four Buzz five
Fizz seven eight fiz Buzz one oh ha one
through 100 that would have been close I
almost didn't follow instructions I was
like why do we have a Fizz Buzz up front
that doesn't uh that doesn't make sense
Okay cool so one two Fizz four Buzz
right five is a multiple of five 10 is a
multiple of five
15 is a multiple of 5 and three so this
is looking correct to me let me go ahead
and submit that so in the last
assignment we kind of uh did a if else
chain within our for Loop but there's
another way that we can write guard
Clauses within Loops um so that we don't
have to necessarily do that if else
chaining um if we don't want to uh the
continue keyword stops the current
eration of Loop and continues to the
next iteration so continue is a powerful
way to use guard classes right so we
write our for Loop and and then if some
condition happens um we can kind of bail
out of the body of the for Loop early
and just continue on to the next
iteration the break keyword is similar
in that it stops the current iteration
but instead of continuing on to the next
iteration it just ends the loop entirely
moving on to the assignment it says as
an Easter egg we decided to reward our
users with a free text message if they
send a prime number of text messages
this year because textio is is run by a
bunch of nerds complete the pr the print
primes function it should print all of
the prime numbers up to and including
Max Max it Should Skip any numbers that
are not prime okay so here's the pseudo
code print Prime Max so let's convert
this pseudo code into real go code so um
for n in range 2 to Max plus one so
we're going to do 4 n colonal
2 N is less than Max + 1
n++ okay if n is two if n is
two n is prime print it fmt do print
line
n and we need to continue right continue
to the next iteration basically saying
okay this n is prime we'll print it and
then we can move on all right if n is
even so so
if n mod 2 is zero right that's an easy
way to check if something is even um and
not make your way onto our programmer
humor if n mod 2 is
zero n is not prime skip to the next n
so uh we do not print and we just
skip
okay next we do a nested for Loop here
for I in range so for I CL
equal 3 to the S < TK of n + 1 okay this
is actually interesting I could use the
math. square root function here that
would be a valid way but if I want to
stay in integer land which I think I do
I can
do
um I can instead Square I so I * I I * I
and see if it's less than
n Plus
right does that make sense so instead of
doing I is less than the square root of
N I can do I SAR is less than
n
right that that makes sense to me cool
um because basically we're just saying
we only need to check up to the square
root of n we know that if we go higher
than that um but like we don't care
about numbers higher than the square
root um cool
next one is
i++ if I could be multiplied into n so
if n mod I
oops if if I goes into n evenly so if
that results in a zero n is not prime
skip to the next
n okay so I can't continue here because
if I continue here here I'll just skip
to the next I and I want to skip to the
next n so I think what I do here is I do
something like is
prime
true here I set is prime is
false and break and then at the end of
the for loop I can say if not is
prime
continue okay
okay uh if n is not prime skip to next n
yeah so we'll break out of this
Loop right we'll break out of this
Loop and then we'll
continue
cool now I just want to be clear there
are other ways to write this function
I'm kind of uh on purpose using lots of
continues and breaks uh so that we can
get some practice with it cool um n is
prime printed so if we get all the way
to here without any of these kind of
guard Clauses being triggered then n is
just Prime so fmt print
line
n let's run that and just sanity check
um our code primes up to 10 2 3 5
7 yep right that's two is a prime number
it's kind of like the only even prime
number and nine is an odd number before
10 but it's not prime because it's
evenly divided into by three right maybe
I should have even explained what prime
numbers are in the first place in case
you're not familiar um there is uh there
is a link here if you want to go read
more about them but basically a prime
number is any number where the numbers
that multiply evenly into it are
anything except one in itself so if it
has any multiples or if it has anything
that multiplies into it that isn't just
one and itself it's Prime right so seven
you can't you can't multiply two into
seven you can't multiply three into
seven you can't multiply four into seven
right um so it's
Prime um let's just uh look at a couple
more of the examples so up to 20 we got
2 3 5 7 11 13 17 19 right again we're
skipping 15 because it has the multiples
three and
five primes up to 30 um again skipping
25 um and also skipping 27 because 9
divides evenly into 27 as as just three
cool so hopefully that makes a bit of
sense and then just kind of as a as an
explanation of of why this pseudo code
Works we're skipping even numbers
because they can't be prime right if it
if two divides into something it's not
prime um we only check up to the square
root because anything higher than the
square root has no chance of multiplying
evenly into n right so like for example
take the number 16 its square root is
four nothing over four could possibly
evenly divide into
16 that the square root couldn't right
so like for example 8 divides evenly
into 16 but that's only because four and
two already
do okay uh we start checking at two
because one is not prime one's kind of a
special case
number okay so this is looking correct
to me I'm going to go ahead and submit
it
let's talk a little bit about how arrays
work under the hood so if you're
familiar with the idea of arrays from
JavaScript or lists from python arrays
and go are similar you can think of an
array is just an ordered list of items
so we usually denote arrays with square
brackets and an array of say three
integers might be something like 2 3 1
right so we've got three integers
stored in our array the first thing is
the integer 2 it's stored at index zero
the next one is a three it's stored at
index one and finally we have a one
stored at index 2 now here's the big
difference between arrays in go and
arrays in JavaScript or lists in Python
in go arrays have a fixed size so the
type of this array would
be three
int so inside of the square brackets we
kind of indicate the size of the array
and then after we indicate the type of
thing in the array so this array's type
is an array of three integers in
languages like JavaScript or again like
lists in Python arrays are kind of
dynamically resize they don't have a
fixed size you can add things onto the
end you can push stuff onto the
beginning but in go arrays are always
fixed so to show you what this looks
like in code um basically we can create
a new array of 10 integers like this it
will initialize all of the indices in
the array to the zero value so this
would be an array of 10 zeros basically
um if we know what we want to store at
each index in the array then we can use
an initialize literal here so we're
saying we have an array of six integers
and in the first index I wanted to two
and then a three and then a five and so
on so let's jump into an assignment and
see how this works the assignment says
when a message is not responded to we
allow our clients to have up to two
additional messages that are sent as
nudging reminders get message with
retries returns an array of three
strings where index zero is the first
message if the first message is not
answered by the recipient we send the
second and then we'd send the third
update get message with reti to return
the three following strings in an array
click here to sign up PR please click
here we beg you to sign up Okay cool so
this is pretty straightforward here
we're just going to
return an array and you can see the
return type up here it's a string array
of size three so we just need to create
an array literal and return it and in
this case we want an array of strings of
size three so array string size three
and we can use these curly
brackets um to be kind of where we put
the string literal so the first string
is going to be click here to sign up
right this is the first message that's
sent pretty please click
here and we beg you we beg you to sign
up and then just remember that in go you
do have to put that last comma um if
you're going to use a new
line okay um pretty straightforward
right let's see how that
works
sending sending to Bob click here to
sign up they responded sending to Alice
click here to sign up pry please click
here they responded okay this is looking
this is looking good to
me going to kind of get a peek at the
uh at the test
Suite cool let's submit it and see how
we did so we've talked about how arrays
are fixed in size and you might be
wondering well that's not very useful
why would I care about an ordered list
of things if I can't even add to or
remove from the list well that's where
slices come into play so let's draw out
a simple
array I'll do it in yellow let's just
say it's storing some numbers we might
have some numbers like six 3 2
6 five and I'll draw the indices let's
do that in pink so the index of the
first item is zero and then 1 2 3 4
right and the size of the array is five
which in go we would write like like
this we would say we have an array of
five items and they are integers Okay
cool so we understand what an array is
but what's a slice well slices in go are
written with this syntax open close
bracket int and you'll notice that the
size is missing okay so a slice is a
dynamically sized flexible view into an
array so slices are built on top of
arrays so that means for example that I
could create a slice that just looks at
this kind of middle
portion of this array if I were to write
this in code I would basically say if
this array is named a so a is this
yellow array here if if I want to create
a slice on top of that array then I
could write
B colon equals
a from we're using square brackets here
index
one up to but not including index 4 okay
so the first the first number is
inclusive the second number is exclusive
and now B is this slice of kind of just
that middle view of the array and here's
the important thing to understand in go
we actually almost never deal with
arrays directly 99 times out of 100
you'll just be working with slices
because slices provide a much better
developer experience they're built on
top of arrays for kind of memory
management reasons which we'll talk
about in just a second but you really
want to be working um for the most part
with slices because you don't have to
worry about that fixed size problem so
just review slices sort of in code um we
can create an array literal like this
right this is an array because we have
we have the uh size of the array there
six integers and then we can create a
slice on top of the array like this cool
um let's jump into the assignment so you
can kind of get a feel for how this all
works okay the assignment says retries
are a premium feature now textos free
users only get one retry message while
Pro members get an unlimited amount
complete the get message with retries
for plan function it takes a plan
variable as input that's a string
matches up to one of these um if the
plan is a Pro Plan return all of the
strings from get message with retries
Okay cool so get message with retries it
looks like
returns an array of three
strings okay let's just jump right into
it if plan is plan
Pro then we'll return all messages right
return all the strings yep and a nil
error otherwise if the
plan is plan
free return the first two strings right
the original string and kind of the one
the first retry message so return all
messages and we're going to slice it
from index zero up to but not including
index two so that'll be indexes zero and
one right two two strings and then we'll
also return
nil and finally if it's neither of those
return an error that says unsupported
plan so return nil errors. new
unsupported plan and nil um is just kind
of the zero value of
slice cool um this is looking good we
need to make sure we import the errors
package let's go ahead and run this and
see what
happens o we got an error cannot use all
messages variable of type
array size three string as slice of
string Okay cool so we have an
array all messages is an array not a
slice right so we actually need to slice
this to change its type but we want all
of the values inside of the array so
we're just going to use this colon
syntax to get access to everything in
the underlying array let's run that
again
cool sending to
osar click here to sign up pretty please
click here so that's two two messages
with no
response um and I'm guessing because yep
ozer is on a free plan Jess Jeff is on a
Pro Plan so he gets all three messages
this is looking correct to me these next
few questions will reference kind of
excerpts from the effective gobook which
is definitely a book I recommend reading
although it is a bit out of date it was
written a while ago um and the authors
have basically made the decision not to
update um the book over time kind of
keeping it as a snapshot so it has a lot
of great stuff in there and go has very
strong backwards compatibility so it's
still a great read um just kind of be
aware that it hasn't been updated um in
a while cool um that said everything we
explained here I'll obviously provide uh
context um for so the thing that's
important to understand in this section
is that slices are references to kind of
what goes on underneath the hood with
arrays so what that really means to you
as a developer is when you use slices
and more specifically when you pass them
around say into a function you're
actually passing a reference which means
if you change the values in that slice
within a function the caller the person
who called your function or I should say
the bit of code that called your
function will actually have access to
those changes it will see those changes
even if you don't explicitly return the
slice again now this is different um
from sort of normal primitive values
which are passed by value you might
remember us talking about pass by value
earlier in the course typically if you
pass in say a string or an integer into
a function and then within that function
you change it the caller won't see those
changes you have your own copy of that
data that's that does not hold true with
slices so just understand that when you
pass a slice into a function it might
get
modified okay cool um so which
references the other is the question do
arrays reference slices or do slices
reference arrays and the answer is that
slices reference arrays the next
question is can multiple slices point to
the same array is that true or false
multiple slices point to the same array
um that is true remember slices are just
kind of a view into an array so it makes
sense that you can have multiple views
into the same underlying array so
multiple slices can point to the same
array now here's that question um that
we talked about earlier a function that
only has access to a slice can modify
the underlying array the answer to this
is that is true even if the function
doesn't return that slice it can modify
the values in the underlying array let's
talk about how slices work um kind of
specifically how work in relation to
your computer's Hardware or your
computer's Ram now Ram just stands for
Random Access Memory it's where
variables are stored the best way to
think about Ram is just a mapping of
addresses so
address to
data right it gives us a place to store
stuff so for example at address
Z we might have some data that
represents I don't know the number four
right and then at address one we might
have some data representing the number
five I don't know I'm just making stuff
up but you get the idea we have
addresses and we have data associated
with that address slices and the arrays
that they are built on top of are stored
in contiguous memory basically what that
means is a slice or an array is an
address in memory where where the slice
or array starts so for example at
address zero we might say this is the
start of a
slice and the slice actually continues
for the next you know several uh kind of
bytes of data let's say that it's a
slice of length three so it actually
would reach across all three of these
addresses so if you have an array or a
slice um and its address is zero and its
length is three then you know kind of
how many uh stores of data in memory um
your slice or array will use now this is
primarily important for performance
because all of the data is stored next
to each other in memory it's going to be
faster to kind of iterate over all of
the values in our slice if we stored
each index in kind of random places in
memory it would take a lot longer to go
collect all of that data just you know
from a hardware perspective however
there is a downside to having to store
all of our memory um kind of next to
each other let's pretend that we have
this slice or this array that starts at
address zero and has a length of three
so it's using kind of these three
addresses in memory or these three bytes
of memory and let's say here at address
3 we're storing a different value maybe
we're just storing I don't know the
number six um but this is a different
variable
this is some other variable the problem
is if we want to expand our slice say
add another value to it we're going to
run into the next thing in physical
memory that's a huge problem we don't
want to overwrite some other variable
just because we're growing our slice so
this is why arrays are fixed in size if
we just can't grow them then we'll never
have this overwriting problem so the the
question of course is how do slices do
it right well slices are built on top of
rays and basically what happens is I can
draw this out for us when we want to
grow a
slice whose underlying array has run out
of room right so we have our slice um
built on top of this length three array
and we want to grow it into a length of
four what happens under the hood and
this all kind of happens without you
seeing it as a developer you'll you'll
see later when we get to the syntax it's
actually really simple um what happens
is this data is copied into a new
location memory so we just take this
four we put it over here five over here
uh actually I'm going to use the same
colors that would probably be easier to
understand so four comes over here five
comes over here three comes over here
and then let's say that we um we know we
want to grow our slice significantly so
the new underlying
array let's say will have a length of
six now so it had three here now the new
array has allocated pre-allocated right
we've essentially reserved memory for up
to six spaces let me draw all of that
out cool and these addresses over here
are going to be totally different right
maybe it's address 15 address 16 17 18
19 and
20 cool so all the data from the old
array gets copied over and now we're
able to expand our slice
our slice to a length of
four right so here our slice was length
three and the underlying array was
length three now we've copied over the
data we've created a much larger
underlying array and now we have a slice
of length four maybe we wanted to append
say the number two to the end of the
slice so again as the developer you're
not going to have to do all of this
array management manually but it's
important to understand that this is
what's going on under the hood because
it has performance implications copying
data isn't super efficient if you're
copying data from one section in memory
to another um over and over and over
again it can slow down your programs so
now that we've covered all of that
memory stuff and how it works under the
hood what does that mean in code right
well this is how we can create a slice
without explicitly creating an array
under the hood right so this will
automatically create an an array under
the hood for us if we use this syntax so
this is basically saying I want a new
slice of integers I want its initial
length to be five so the length of the
slice right the length of the view into
the array will be five and the capacity
will be 10 so the capacity is kind of
the total space that we have to grow the
slice until we need to allocate a new
array under the hood so you could almost
think of this capacity is just the
length of the underlying array now I
told you that you don't need to think
about the size of the under the Ling
array and that is true typically you'll
actually use this syntax where you don't
even specify a capacity so if you do not
specify a capacity it defaults to the
length so the kind of length of the
underlying array for a slice of integers
with length five will just be five and
if you grow it past that the memory
copying will happen and you'll create a
new underlying array now you might be
thinking oh that's terrible for
performance in reality it's not that bad
the only reason you would start to kind
of fuss with specific capacities and
optimizing your memory copying is if
you're having performance problems
generally speaking the convenience of
keeping your code simple and easy to
understand is going to outweigh kind of
the performance cost that it'll have
it's it's a very small one generally
speaking and then it's also just worth
pointing out that we can also create
slice literals right so this just
creates a new slice slice of length
three um and initializes these three
values into that slice rather than what
would happen up here which is that we
create kind of all of the zero values um
to fill out the slice kind of of the
given length so this would be a slice of
five zeros right and then to just point
out two more things regarding syntax
there is a built-in length function that
Returns the length of a slice and a
built-in cap function that Returns the
capacity so now that we are experts on
slices let's jump into the
assignment says we send a lot of text
messages at texo and our API is getting
slow and unresponsive so I just
mentioned how you probably shouldn't
worry about performance well here's a
case where you should worry about
performance right you shouldn't worry
about performance until well it starts
to become a problem so we're starting to
have a problem with um this memory
copying being slow so we've been asked
to preallocate our
slices it says um if we know the Rough
Side of the before we fill it up we can
make our program Faster by creating the
slice with that size ahead of time all
right so complete the message get
message costs function it takes a slice
of messages and returns a slice of
message costs FL 64s right reallocate a
slice for the message cost with the same
length as the message slice let's go
ahead and start off with that so
costs we'll make a new slice and it's
going to be a slice of float
64 and a length of length messages right
same same length cool um now we want to
fill the co fill the cost slice with
cost reach message so let's go ahead and
iterate over each message so for I colum
equals
0 I is less than length of
messages
i++ let's get the message at that index
messages
zero oh not a zero excuse me at
I okay the cost in the cost slice should
correspond to the message and the
message slice at the same index the cost
of a message is the length of the
message multiplied by 01 so cost equals
the length of the message multiplied by
01 that's going to need to be cast to a
float
64 perfect cool and then we just need to
save that into the costs slice so costs
at I equals
cost right correspond with the same
index and we know that that um that
index already exists because we
pre-allocated it to the correct size
cool so now we can just return costs
let's go ahead and run
that and see if these make
sense okay these costs line up with the
length of the messages so this is is
looking good to me let's review the
difference between the length and the
capacity of a slice sometimes this can
be a little bit confusing the length is
the thing that you're going to care
about most often the length just tells
you how many things are in that slice
right if I have five items then the
length of the slice is going to be five
the
capacity the capacity of a slice reports
the maximum length the slice can assume
right before it gets kind of reassigned
into a new array so capacity is really
again only something you're going to
care about if you're worried about
performance but the length you'll be
concerned about just for kind of normal
business logic reasons right just
because you want to know how many things
are stored in your slice so to answer
this question it says what does the cap
function return answers are the last
last element of a slice or the maximum
length the slice of the slice before
reallocation of the underlying array is
necessary the answer is going to be the
maximum length the next question is what
does the length function return current
length of the slice or the maximum
length of the slice before the
reallocation is necessary and the answer
is going to be the current length of the
slice and we haven't really talked about
this yet but I'll just kind of inform
you as we answer this question says what
do length and cap the two functions do
when a slice is nil do they Panic or
return zero and the answer is that they
return zero they are safe functions to
call um they won't make your code panic
and error and crash right um I don't
know how much we've talked about
panicking up to this point in the course
but panicking just means runtime error
that's unrecoverable
unrecoverable um generally speaking you
don't want your code panicking and you
want to write your code in such a way
that it can't Panic um so again length
and cap here um are safe they will never
Panic if um a slice happens to be um the
zero value which is nil time for some
vartic functions um this sounds like a
really complex thing but we've actually
been using vartic functions up to this
point in the course because the S printf
print F print line all of those
functions are actually vartic so a
vartic function receives the vartic
arguments as a slice let's take a look
at the syntax so here we have a sum
function and its function signature is
just maybe a little bit different than
you're used to basically we have this
nums uh parameter which is of type dot
dot do int now here's the thing dot dot
do
int as far as the function definition is
concerned is just a slice we treat this
just like we would if it said you know
square brackets int it's it's just a
slice of integers so you're probably
sitting there thinking well why can't I
just use a slice of integers why can't
we just keep it simple why do we have to
do everything differently ah okay well
uh don't don't worry too much um the
difference is on the caller's side so
the function definition is the same
whether you use dot dot dot int or
square brackets int but if you use a
vartic function then the calling code so
the code that uses the sum function can
actually pass in kind of any number of
arguments and they'll come into the
function as a slice of integers so here
we could call for example sum 1 2 3 and
the sum function gets a slice of
integers with you know the values one
two and three in the first three indexes
of the slice um this means that the
caller could also call some one comma 2
right and we'd have a slice of length
two so it kind of just gives the collar
of the function a different syntax um
and specifically kind of a more flexible
syntax uh for how they're able to pass
in sort of a dynamic number of arguments
into the function now again we've
already been using vartic functions you
probably remember the print line
function right this is how we print text
to the console um it's a vartic function
dot dot dot interface right so it can
take any number of arbitrary inputs and
it sort of prints them all out with new
lines in between each element now along
with vartic function definitions we also
have another operator called the spread
operator and the spread operator is kind
of like the inverse
of a vartic function by using the spread
operator we're able to take a slice of
values and pass them into a vartic
function so this isn't the intended use
case 100% of the time otherwise you just
Define your your function to take a
slice of strings and you'd pass in a
slice of strings but if you do have a
function that is already vartic and you
want to pass in a slice as the variable
part then you can use this spread
operator this trailing dot dot dot to
sort of spread out in this case this
name slice into the vartic function
let's get our hands dirty with this so
it says we need to sum up the costs of
all individual messages so that we can
send an end of Monon Bill to our
customers complete the sum function so
that it Returns the sum of all of its
inputs cool so this is going to be very
similar to this sum function right the
the difference here is that we're using
float 64s and we're going to write it
from scratch uh so that we get our own
our own crack at it okay so first things
first uh let's create kind of the
default sum which is going to be
0.0.0 so uh let's say
total now remember we can treat nums as
just another slice of float 64s so 4 I
colon equals 0 I is less than length of
nums
i++ total plus
equals nums at
I
right return
total cool pretty straightforward um
let's go ahead and run
that so summing three costs bill for the
month $6
5 costs 15 10 costs 55 do these match up
looks like yep that should be
six 6 + 4 + 5 that's 6+ 9 that's 15 Yep
this is looking good I'm going to go
ahead and submit that now I told you
that resizing slices is possible we just
haven't really done it yet um that's
what the append function is for so the
built-in append function is actually a
VAR adic function but it allows us to
just add new things to the end of a
slice and automatically takes care of
adjusting the length and the capacity of
the slice accordingly right allocating
new underlying arrays as necessary now
here are your syntax options for using
the append function if you just want to
append one thing then you'll use this
top option let's say we have a slice
called slice we want to append a
variable called one thing to it
basically we call the append function we
pass in the slice we want to append to
as the first uh item we pass the
variable that we want to append onto the
end as the next thing and then we
reassign back into that same slice now
because append is a vartic function we
can append multiple things right so um
if we want to append the first thing and
then the second thing after that um we
could do it like this and obviously we
could also use the spread operator if we
want to to be 100% honest though 99% of
the time you'll be using this first one
because you'll just be appending items
one at a time let's hop into the
assignment it says we've been asked to
bucket costs for an entire month into
the costs that occurred on each day of
the month so complete the get cost by
day function it to return a slice of
float 64s where each element is the
total cost for that day okay so we have
like this giant list of costs and we
need to kind of uh condense all the
costs that happened on a specific day
into one index for that day makes sense
to me the length of the slice should be
equal to the number of days represented
in the costs
slice including any days that have no
costs up to the last day represented in
the slice Okay cool so if we have costs
just for say the first month or the
first day of the month and the fifth day
of the month then we should have um kind
of five indexes in our resulting array
with kind of a non-zero value in the
first and the fifth indexes zeros in the
middle and nothing after that okay here
we've got an example
um given this input so day cost so we
have costs on days on days 0o one and
five this would be the resulting array
right $4 on the first day 5.2 on the
second day because we sum those right
and then on that last day we'd have
2.5 cool let's write the
function so first things first we're
going to need
um we're going to need a slice to append
into so costs by day we'll make a new uh
a new slice of float 64 and here I'm
just using the slice literal syntax um
instead of the make function they're
pretty dang
similar now we're probably going to want
to iterate over all of the cost so for I
equals 0 I is less than length
costs
i++ costs at
I okay so a cost is this structure here
and we can take a look at the day
there um we're going to need to figure
out basically when we're appending to
this costs by day slice and when we're
just adding to an existing index so I
think the easiest thing to do would
basically be to
say um
if day is greater than the length of
costs by
day actually it's going to be a four so
like while the day is greater than the
length of cost by day we're going to
append cost by day equals append cost by
day
0.0 right so in effect this for Loop
here just says if I've encountered a day
that I don't yet have room for I'm going
to grow the slice by just appending
zeros until I have enough room so once
we're done with that Loop we should be
able to assign directly into the costs
by
day I basically saying cost by day plus
equals
cost.
value right because it starts at zero so
then we can just
add we just add the cost and again if if
the day is less than cost by day then we
just don't do this step we just skip
over this for Loop so that's looking
correct to me I'm going to go ahead and
return costs by
day and let's run that code see what we
get undefined day what did I screw up
here yeah needs to be cost. day right
access that
field invalid operation cost by day plus
equals cost. value so cost. value is a
float
64 costs by
day is a slice of flow
64s right because this is a slice so
costs by
a
at at cost.
day
right right because our the slice that
we're
returning um the indexes in each or the
indexes of the slice represent each day
in the month so we need to index back
into the
day okay try
that whoa what do we got
Panic runtime error index out of range
zero with length
zero
okay what did we screw
up
so somewhere we are indexing into a
slice where the value doesn't like the
the the index that we're indexing with
is outside the range of the slice so um
we haven't really talked about this this
is probably great that we ran into this
bug um if you try to index which is this
operation here so like this is indexing
into index five right but here we're
indexing into cost. day which holds an
integer um if you try to index into an
index that is outside the length of the
slice so you have a slice of length say
three and you try to access index six
then you'll encounter this error that
that's what we're seeing here so it
looks like that's what we are doing
so for cost. day is greater than the
length now let's think about that if the
day is five and the length is
five we are not going to do our growth
so I'm actually I think I have an off by
one error here right I actually what I
actually want to do is say if the cost
if the if the cost. day is greater than
or equal to the length right because an
array or a slice of length four actually
only has
three um kind of indexes I mean it has
four indexes but they start at zero so
like length four the indexes are 0 1 2
and three right let's go ahead and run
that
again cool no more
Panic day one $1 or day Zero $1 day one
510 day 2 250 let's see if that lines up
with the test
Suite so here it looks like Day Zero
should have one day one should have yep
5.1 if we add those together and day 3
should have 5 point what's that
6.3 yep 6.3 okay this is looking correct
to
me slices can hold other slices right
this kind of creates a 2d Matrix um of
values right so for example here a 10x10
Matrix of integers would look something
like this where the First Slice is just
a slice of slices hopefully that makes
some amount of sense if not don't worry
we'll get into the assignment here in
just a second so it says we support
various graphs and dashboards on Tex
that display message analytics for our
users the UI for our graphs and charts
is built on top of a grid system let's
build that grid logic so this is super
common in graphics development right
we're kind of building on 2D screens so
we need sort of an X and A Y um cell
within a matrix which is again often
represented as a slice of slices the
assignment says complete the create
Matrix function it takes a number of
rows and columns and returns a 2d slice
of integers where the value of each cell
is I * J where I and J are the indexes
of the row and column
respectively okay cool so create Matrix
we get two integers representing how big
we want the Matrix to be and we just
have to return the Matrix Okay
cool
so let's go ahead and start building
that out first we need to create um or
initialize the Matrix so let's say
Matrix slice let's use make make slice
slice
in right and we can just initialize it
to a length of zero it doesn't matter
we're going to grow it and four I col
equals 0 I is less than length of
rows
i++ and then we're going to need an
inner for
Loop manage J which is going
to deal with the number of columns
columns
j++ okay so what do we do
um when we get to the first row well the
first thing we need to do is make a new
slice to represent the row so we'll say
row make and we can just
do a a single slice of integers again
row uh length of Zer is fine um we could
pre-allocate but it probably doesn't
matter too much at this
point um and then for each J a we're now
going to append into the row so we do
row equals
append
row I * J I believe was the formula
right we want every cell to be the
result of I * J so the values will sort
of grow out um as the Matrix gets larger
and
larger cool so that's adding the like
this inner loop is adding values to each
individual row and then we just need to
append the row to The Matrix so
Matrix equals append
Matrix
row and then when we're done appending
all of the rows to The Matrix we can
return the Matrix
itself go aad and run
that invalid argument row variable of
type int for length see what we got
here where did I screw up
this time
right I don't know why I'm checking a
length here Rose is just an integer it's
not a slice with a size let's try that
again cool creating a 3X3
Matrix does that math check
out 1 * 1 is 1 1 * 2 is 2 2 * 2 is 4 yep
that looks good cool let's submit it I
want to show you a common Pitfall that
you might fall into uh when dealing with
slices and how you can just outright
avoid it um this is something you should
pretty much never do when you want to
append to um other slice in this case
you want to append elements to other
slice you should always reassign into
the same slice so this should read other
slice equals append other slice element
you don't want to be pending um kind of
into one thing and reassigning into a
separate slice you'll run into some bugs
and we're going to talk about how that
works right now so in this first example
we have these slices a b and c and by
the way I would highly recommend coming
here on bootd so that you can actually
kind of look and puzzle through what's
going on in the code rather than just
seeing it on the screen um but basically
we have these slices and when we append
to them
we're we're breaking the rule basically
we're uh appending for example here um
four onto a but then returning the
value the value returned by a pend we're
kind of saving it into a new slice
called B which again you you generally
should not do you should just save it
back into the same slice a um but
anyways we're going along um kind of
doing that pattern and at the end you'll
notice that nothing too terrible
happened um basically we ended up with
an a with three zeros B had four
appended to it properly and C had five
appended to it properly so you could
kind of take away from this example well
maybe there's nothing wrong maybe we can
just kind of uh break the rule and
append into new slices seems like
everything's working like I'd expect and
it's worth pointing out that we can even
print out the addresses in memory of the
slices B and C and see the addresses are
different in other words when we called
this append functions where we append
five onto um you know the slice a and
save it back into the variable C we can
see that actually C is being kind of
copied into a new location so again
everything's kind of working as we'd
expect now it's really in this example
two that something very strange happens
if we look
down here we're a pending onto
J the value four and then we're
appending onto G here the value five but
the interesting thing is that after we
append five to G if we print out J again
we can see that J was actually changed
kind of under the hood right up here J
had four in its fourth
index and then we never touched J again
directly but by appending onto G we
actually screwed up J we mutated J and
the reason for that is because in this
example because of the way we've
sloppily used the append function G and
J actually point to the same address in
memory so mutating G changed J now I
already mentioned that we were doing the
same thing up here right we were using
the same kind of sloppy use of the a
pend function that I'm recommending to
avoid but we only had the bug in example
two and the reason for that is because
the original slice in example two has a
capacity of
eight while the original slice in
example one had a capacity of three so
what that means is when we used the
append function in the first example
once we went over the capacity we
allocated a new underlying array which
is why we got different memory addresses
and so when we mutate C here C is in a
different place in memory than b so
they're kind of operating independently
which is again what you usually want in
your
code however because the capacity was
already large enough in example two
there was no need for the append
function to create a new underlying
array so G and J point to the same the
same array in memory again which means
if we mutate G under the hood we just
mutating J so again the way you can
avoid all of this
headache is to just not do this right
append onto the same slice pretty much
every time unless you have like some
crazy specific reason not to do so which
like I'm skeptical that exists I
certainly haven't run into it in you
know many years of writing application
code in go so the question for this quiz
is why is five the final value in the
last index of array J right so why why
did we have a five here even though we
appended for um and the answers are J
and G point to the same underlying array
so G is aend over OJ the go team is
trolling I think that's obviously not it
uh and because a pend only works
properly when the number of elements is
less than 10 uh that would be very silly
so it's it's definitely this middle one
we're overwriting the same location in
memory next question on the same topic
is why doesn't the bug regarding slices
J and G in example two occur in example
one as well and the the answers are
because there are fewer elements and
goes runtime can't handle more than
eight elements that would be awful um or
the array's cap is exceeded so a new
underlying array is allocated and and
that one is the answer so the next
question is how can you best avoid these
types of bugs don't use the append
function always assign the result of the
append pend function back into the same
slice or always assign the result of the
append function to a new slice well it's
not using a new slice that's how we got
into this trouble in the first place and
we definitely want to use the append
function it's pretty useful so again
always assign the result of the append
function back into the same slice you'll
avoid a lot of headache that way finally
some syntactic sugar to help us iterate
over the elements of a slice you've
probably been wondering for the majority
of this chapter uh do I have to do this
I equals z i is less than the length
every time I want to iterate over the
elements of the slice other languages
have syntactic sugar to make it easier
so does go so the syntax is pretty
simple um this is it right here and
basically by writing it this way index
at each iteration of the loop will be
equal to the index in the loop starting
at zero right so 0 1 2 3 and then
element is the value associated with
that Index right and then obviously
slice here is the name of the slice so
range is a real
the interesting keyword that allows us
to iterate over everything stored in a
slice to give a more concrete example
here we have a slice of strings called
fruits and we can arrange over the
fruits and if we print I and fruit in
this example that we print zero apple
one banana two grape and so on let's
jump into the assignment it says we need
to be able to quickly detect bad words
in the messages that our system sends
complete the index of first first bad
word function okay so that's this um it
finds any bad words in the message if it
finds any bad words in the message it
should return the index of the first bad
word in the message slice this will help
us filter out naughty words uh from our
messaging system if no bad words are
found will return negative one instead
use the range
keyword Okay cool so the bad words
themselves are defined for us and passed
into our function and then the message
itself is already broken up into words
it looks like and passed in is an array
of strings right so we can I'm kind of
just figuring this out by looking uh
looking down here so we have a slice of
bad words and a slice of words in the
message hey there John Okay
cool so um let's
start by iterating over all of the words
in the message right seems like a
reasonable place to start so
for I word colon equals range
message and then we want to check and
see
if if one of these words is equal to one
of the bad words so we're going to use a
nested Loop
actually we'll do
J bad word in range bad
words right and then here we can say if
word equals equals bad
word that means we found the bad word
and we can
return we want to return the index so we
return I right that would be the index
of the bad
word otherwise if we don't find a bad
word we'll just keep going um in fact
we'll just keep going through all the
bad words and then we'll keep going
through all the rest of the
words and if we get to the end of
everything without finding any matches
then we can just return negative
1 makes sense right
cool let's run
that J declared not used right um so
we've come across this syntax before we
can ignore variables with an underscore
right let's run
that let's take a look at our test Suite
scanning message hey there John for bad
words index negative one that means none
were found right which makes sense we
didn't have any scanning message uh oh
my freck for bad words brick is a bad
word index 3 okay this is looking good
to me I'm going to go ahead and submit
that if you're familiar with object
literals in JavaScript or python
dictionaries then maps and go are
essentially the same thing maps are just
a way to associate a key with a value so
let's take a look at this example we
create a new map with this syntax here
we're going to use this built-in make
function and then pass in the type of
the map so map of string to integer so
we're mapping strings to integers and
we're going to say inside of the ages
map we're going to set the key John to
the integer 37 so again we're mapping
that name John to the value 37 rather
than creating an empty map and then kind
of adding key value pairs one at a time
we can also declare the entire map up
front and use kind of this colon syntax
to separate the keys and the values
and then we can also check how many keys
I guess keys and values are in the map
by using the built-in length function
similarly to how you would use it on a
slice um so in this case we create a new
map we create two keys each with their
Associated values and then by printing
the length we'll just print the number
two so let's jump right into the
assignment that's usually the best way
um to get an idea for how all the syntax
Works assignment says we can speed up
our contact info lookups by using a map
look up a value in a map by its key is
much faster than searching through a
slice so when we look up something in a
map by a given key that's going to be an
instant
lookup if we had stored it in a slice
then we'd have to search through the
entire slice sort of index by index
looking for the value that we want so
Maps can be a great way to make our code
more
efficient the assignment says complete
the get user map function it takes a
slice of names and a slice of phone
numbers and returns a map of of name to
user structs and potentially an error
okay so let me expand our coding window
here okay so we're returning a map of
string to user structs looks like the
user struct is defined right here if the
length of names and phone numbers is not
equal return error with the string
invalid sizes the first name in the name
slice matches the first number and so on
Okay cool so first things first we're
going to have to create a new map so
we'll do um user map
colon equals and I'm just going to
create a literal a new map literal
actually no I won't I'll do I'll use the
syntax from up here this works just fine
so we'll use the make function so make
map of string to
user right so that's now an empty
map and then what we're going to do is
check and make sure that these are the
same length so if the length of names
does not equal the length
of phone
numbers then we'll return nil so nil is
the zero value of a
map we could also return an empty map of
the same type but I'd say you should
prefer nil um for slices and maps and
things um and then the
error should say invalid sizes so
errors. new
invalid
sizes we'll import the errors package
Okay cool so if we get down to line 13
now we should know that both of these
slices names and phone numbers are of
the same size which means we can Loop um
like this so four I starting at zero and
I is less than the length of it doesn't
matter because they'll be the same right
length of
names
i++ then we can say
name is names at
I and we can say fun phone
number phone
numbers at
I
okay and then we're going to want to
insert values into the user map so we'll
do user map at the key in this case the
key is the name right so key is the name
and we'll set it equal to a new instance
of a user struct so
user and the user ruct has two Fields
name which will just again be the name
and the phone
number uh which will just be this phone
number
here
cool so that should fill the entire user
map with all of the names and phone
numbers and then by the end we should
just be able to
return user map
nil okay cool I think we got everything
let's go ahead and run that
and see what we get so creating map key
John value name number key Bob value
name number this is looking this is
looking correct to me here we have
creating map with invalid
sizes um if we take a look at the
tests uh yep that makes sense the second
test has two names but three numbers so
I would expect it to get that invalid
sizes error cool let's uh let's submit
that so the primary way you interact
with maps is just by setting and
deleting uh values at a given key right
they're very associative right a value
maps to a key but there's no other
ordering Maps aren't ordered from you
know index 0 1 2 3 uh like a slice is
everything's unordered you're literally
just mapping values to Keys Okay cool so
let's take a look at some of the
different syntax that we can use to
interact with maps so we can insert an
element by just setting the key equal to
the value um we can get an element out
just by accessing it directly at its key
we can delete Elements by using the
built-in delete function where we're
passing in the map itself and the key
that we want to delete notice that we're
not passing in the value at all we're
just passing in the key and then we can
also check if a key exists by parsing
the return value of kind of this access
syntax into two separate values where
the first value will be the element
itself and the second value is a Boolean
if the Boolean is true then the element
will be whatever element was stored of
that key however if the Boolean is false
that tells you that that key didn't
exist in the map and the element will
just be the zero value for its type
let's jump into the assignment so it
says in fact let me uh let me resize
this a
bit says it's important to keep up with
privacy regulations and to respect our
users data we need a function that will
delete user records okay complete the
delete if necessary function um it takes
a a map of users and a name which I'm
going to guess is the key in the map and
it will return a Boolean saying whether
or not the user was deleted and then an
error if something went wrong okay cool
so if the user does not exist in the map
return the error not found oh
interesting so if we're trying to delete
something and it's not there um that's
an error if they exist but aren't
scheduled for deletion then return
deleted as false so we this will be
false um and there won't be any errors
um but if they exist and are scheduled
for deletion then we'll return uh the
deleted Boolean as true with no other
errors okay that's making sense to me
and then a note on how maps are passed
into functions is that like slices maps
are actually passed by reference so even
though this function delete if necessary
does not return a map if we mutate this
users map it will be mutated for the
color of the function so that's how
we're able to delete something from the
map even though we're not returning the
map once we're done with it so the first
thing we need to do is just check and
see if the name that we're trying to
delete exists so let's do this if blank
okay colon equals
users at
name not not okay so uh this is that
kind of special if statement syntax
right um where we're uh actually
ignoring the value at self um we're just
interested in that Boolean value telling
us whether or not this um this key
exists in the map and if it does not
then we're going to go ahead and return
uh return the error not found so false
because we always do zero values for
everything besides the error and then
errors.
new not found and we'll make sure to
import that errors
package okay cool and again I'm just I'm
just uh using this syntax here to check
if a key exists
cool moving on okay so if we get past uh
past that guard Clause to line 12 then
we know um that the user exists so um at
this point ah we need to check if
they're scheduled for deletion so I'm
actually going to switch up my syntax
here the user struct has the scheduled
for deletion Boolean so I'm going to go
ahead and do this on a separate
line and I will save that user so I'll
do um existing user
and then we'll just do if not
okay so that down here now I can use
that existing user struct so I can say
if existing user. scheduled for
deletion so if they exist and are
schedul for deletion return deleted as
true with no errors and delete the
record from the map so we need to use
this built-in delete function so we'll
delete
users name and then we'll return true
n all right so if they are scheduled for
deletion we'll delete them and report
back that we deleted them cool um
otherwise if they're not scheduled for
deletion return deleted as false with no
errors so
return false
no okay that looks right to me let's go
ahead and run
that attempting to delete John deleted
JN attempting to delete musk not found
Santa not found attempt to delete Kade
did not delete Kade Okay cool so I mean
all that look it looks like all the
cases were covered let's just come down
here and take a
look okay so we had a John and JN was
deleted and then attempting to delete
musk there's not actually a musk in the
map so not found makes sense um there's
also not a Santa and Cade was not
scheduled for deletion so he did not get
deleted and then the final map still has
those keys that looks correct to me in
the this next section we're going to be
covering some of the material from the
go blog now I'm not going to read all of
this out uh that would be a little dry
but I definitely recommend reading this
over if you have the time otherwise I'm
going to cover kind of the key points so
the first question is what makes a type
qualify to be able to be used as a map
key okay so any type can be used as a
map value but not every type can be used
as a map key and that's because map Keys
make may be of any type that is
comparable so things like strings
booleans um numbers all those can be
compared for a quality what cannot be
compared for equality are slices maps
and functions and kind of one of the
reasons for that is slices and maps and
functions they're kind of just pointers
to addresses in memory so if you compare
one slice to another slice you're not
really comparing the underlying values
you're actually comparing kind of where
those two slices are stored in your
computer's
RAM and because they're stored in
different places you'll actually get two
slices with maybe the exact same values
stored in them say slice one has the
numbers one two three in the first three
indexes and the second slice has the
same they might still be compared as
unequal because they're stored in
different addresses in memory so I mean
all that being said long story short you
just can't use slices and maps maps and
functions as map keys so to answer the
question what makes type qualifi to be
used as a map key the type is comparable
this next question is interesting and
kind of plays on the idea that we can
Nest Maps um kind of one inside the
other or we could do something that's
arguably simpler let me show you what I
mean so here is an example of a map of
strings that maps to another map which
maps from strings to integers so like
the first string kind of maps to the
second string which maps to an integer
if you want to think about it that way
if you're familiar with Json objects
this would be like mapping Json objects
kind of down multiple uh levels of keys
so in the example given in the go blog
essentially what's being said is we're
mapping um two strings to a count and so
what that looks like in code is if we
want to get access to the count we
actually have to go to the map index the
nested map with a key and then index
into that next map with another key
before we get the value out now this
looks simple on the surface but what
actually happens is when you need when
you go down those nested levels you
actually can't be sure that the inner
map exists and So to avoid panicking
your code will actually Panic by the way
if you try to access a key in a map that
is nil So to avoid panicking you have to
check and make sure that those nested
Maps actually exist which just results
in a lot of extra code so while nesting
Maps definitely works and I've used it
quite a bit there can in some instances
be a simpler way which is to actually
use a struct as a key notice that a
struct was not named as one of the
non-comparable types structs are
comparable so if you want to create a
map that kind of has multiple keys that
kind of combine together to form a
composite key then just create a new
struct right with two values inside of
it and those two values kind of unique
together create their own key so this
simplifies a lot of things where now we
can have one map and we can use that
struct key to create kind of uniqueness
across two different values this might
be useful for example if you're trying
to create a map That's Unique for first
last name combinations right you want
Lane Wagner stored in two different
fields first name and last name to be
unique together so to answer the
question which is simpler to use a
struct directly as a key or to Nest maps
and um the authors of the go blog and I
would agree argue that using a struct
directly is going to be simpler let's
jump right into this next assignment
says we have a slice of user IDs okay so
those are strings and each instance of
an ID in the slice indicates that a
message was sent to that user we need to
count up how many times each user's ID
appears in the slice to track how how
many messages they received implement
the get counts function okay so that's
this one it should return a map of
string to int so that each int is a
count of how many times each string was
found in the slice okay cool simple
enough so let's create a new map so this
will be the counts map and it's going to
be a map of string to
integer and then let's Loop over all of
the user IDs so four blank user ID
in range user
IDs we're just we're going to ignore the
index because we don't care about
it and the first thing we're going to do
is check to see if um a value in the map
for the given user ID already exists so
count okay colon
equals user
IDs sorry
counts at user ID
right cool um in fact now like now that
I type that out I'm not actually sure
that I need this because if I don't use
this this access will still work it's
just if if the key didn't exist count
will be zero which is fine so actually I
I don't even think I care um cool so we
get the count it'll be zero if it didn't
yet exist and then we just increment it
by one so count Plus+
and then we'll save it back into the map
so counts at user ID equals count so
we're just grabbing it out incrementing
it by one and putting it back that seems
like what we want to do right and then
at the end we'll just return
counts
cool let's go ahead and run that and see
what we
get Jing counts for 10,000 user IDs
Counts from selected IDs 0 0 has 31 f F
has 27 DD has 37 okay and if I look down
here it looks like the IDS are being
generated
randomly cool so let's go ahead and
submit it because this feels
right cool we're good to go this next
section is about a piece or an excerpt
from the effective go book um that is a
book that i' highly recommend reading um
some of the stuff's a bit outdated
they've made the decision not to
continuously update effective go but it
is an open-source book you can go read
the whole thing for free um and the link
is right there so go check that out if
you're interested um that said you don't
need to I'll be talking through um the
parts that we care about here so Maps
can have at most blank values associated
with the same key the answers are one
any number of three and two well maps
are associative right they map a single
key to a single value and you can't have
duplicates of the same key in a map that
wouldn't make any sense right then when
you put in a key you would maybe get
back a slice like that doesn't quite
work so the answer is Maps can have at
most one value associated with the same
key the next question says attempting to
get a value from a map where the key
does not exist Returns the closest Value
panics or Returns the zero value um
that's covered here in the section on
missing keys but basically to summarize
if you attempt to get a I mean we
actually covered this in the last um the
last coding assignment but if you
attempt to access a key in a map where
the key doesn't exist you'll just get
back the zero value so for example if
it's a map of strings to integers and
you access a string that doesn't exist
you'll get back as the value zero and
that's kind of nice just because
accessing values in a map is a safe
operation your code will not will not
panic so to answer the question it
Returns the zero value and it's also
worth pointing out really quickly that
if the map doesn't exist so not that the
key that you're trying to access doesn't
exist but if the map itself doesn't
exist if it's a nil map then your code
will Panic so that is a dangerous
operation you want to make sure you're
always accessing values in maps that
have been initialized the next question
is a function can mutate the values
stored in a map and those changes blank
the colar affect or do not affect the
collar now the answer to this one is in
this first section that Maps like slices
are references so when we pass a map
into a function that function can change
what's in the map and those changes will
be visible outside of the function this
is different from Primitives right
strings integers um booleans those are
all passed into functions by value if
you mutate them within a function uh the
the the CER of that function will not
see those changes unless you return
those values but the same does not hold
true for slices and maps so to answer
the question uh a function can mutate
the values uh stored in a map and those
changes they do affect the CER the next
question says what does the second
return value from a retrieve operation
in a map indicate so let's take a look
at this code example here so we've got
this time zone map where we're accessing
the map at a given a given key and it
can return two values the first value is
the value associated with the key and
the second value is a Boolean right we
used this in the last coding assignment
so a Boolean that indicates whether the
value at the key is a nil value nope
that's not accurate a Boolean that
indicates whether the key exists that's
the
one let's practice with some nested Maps
so like we talked about earlier Maps can
contain other Maps as their values not
as not as their keys right so let's jump
into the assignment says because texo is
a glorified customer database right I
mean we're sending sms and email
messages to a giant list of customers or
basically just a big customer database
um we have a lot of internal Logic for
sorting and dealing with customer names
complete the get name counts function it
takes a slice of strings
names and returns a nested map this is
going to be very similar to the last
assignment we did um but this time it's
going to be a nested map where the first
key is all the unique First characters
of the names and the second key is all
of the names themselves so this could be
useful if for some reason we wanted to
get access to all of the names that
start with a very quickly so to kind of
visualize this we've got this example if
the input names slice is this slice here
Billy Billy Bob Joe it would create the
following nested map so the first key is
just a
letter the second key is the full name
and then the value is um the number of
times that name showed up in the
original list and then it's worth
pointing out here that the return value
is a map of runes to a map of strings
and integers in go we often just
represent individual characters as runes
rather than strings of length one it
just gives us a little more um kind of
assurity in our type system okay so
let's start by creating a new top level
map so we'll call it counts and we'll
make that full map now it's important to
understand because this map contains
Maps inside of it we we will need to
continuously initialize new maps I'll
show you what that looks like so now
let's just uh loop over all these names
so
for um we don't care about the index
name and range
names Okay so we've got a name the first
thing we need to do is actually check
and see if we already have a map
associated with the first character of
this name so let's get that first
character let's do um if we probably
need to do some safety checking so like
if length of
name actually the easiest thing to do
would just be if name is the empty
string then we'll
continue we don't care about blank names
they they don't do anything for us right
so we'll just we'll just skip them great
if it's not the empty string then we can
say first character is name zero okay so
that gives us the Rune at
um the first index in the
name and then what we can do is look up
in our map so counts at first oops
counts at first
character um we can see if the inner
map
exists so if it does not exist not if
not
okay let's just keep it simple if it
doesn't exist we'll just initialize it
so if it does not exist we'll say counts
at first
character equals and we'll initialize
the new inner map so we just need a new
map of string to
integer at that first character key
right right in fact to be explicit I'm
going to ignore the inner map
there so by the time we get down here to
line 18 we should be 100% certain that
counts at first character contains an
initialized map so then we can just
simply do counts at first character
at
name
Plus+
right because we've we've made sure that
the inner map exists and if the name key
doesn't exist it will return a zero
which we could then increment and save
back into the map so that should work
just
fine at the end we can return
counts cool hopefully that makes
sense and we've got some test cases down
here Matthew George Drew Philip Bryant
and then a big list okay cool let's run
it and see what
happens whoops cannot use first Char
variable of type bite as Rune
value right so when you you index into a
string right so name is a single string
in go then it is a bite type um but we
want a rune type
so going go ahead and cast
it and run it
again cool uh Jing counts for 50 for the
first 50 names count for M Matthew is
three G George is One D Drew is four we
don't have any pan X so these counts
look totally plausible to me let's go
ahead and submit it it's time to talk
about first class and higher order
functions which are just really kind of
confusing complex terms for a much
simpler idea which is functions as data
a programming language is said to
support first class functions if it
allows you to pass around functions just
like you pass around any other variable
storing essentially an entire function
at as a value and then a function that
uses that first class function so a
function that accepts another function
as a parameter or returns a function as
a return value is said to be called a
higher order function let's take a look
at a concrete example okay so here we
have two functions add and multiply um
these are very simple functions you
should be able to understand uh what
they do just by looking at them for a
second um but here we have kind of an
interesting function it's called
Aggregate and it takes as input three
integers a b and c and it takes as input
an entire function right so it takes a
function and we're calling it here
arithmetic it's the fourth parameter to
the aggregate function and this function
has a specific signature this function
arithmetic must take as its parameters
two
integers right and return return an
integer itself and then the aggregate
function just returns an integer so what
does the aggregate function do well it
calls the function it was given Twice
first it calls it once with A and B and
then it calls it again with the results
of A and B and C so for example if we
call aggregate with the numbers 2 3 4
and the function add then it will add
all three of those numbers together
right which would in this case print n
and then we can also use that same
aggregate function with the same numbers
2 3 and four but this time pass in that
multiply function and here we'll get 24
right because we're multiplying um all
of the variables together instead of
adding them it's okay if this is a bit
confusing uh if this is the first time
you've worked with functions as data
take a second to really kind of stare at
this code and and figure out these this
crazy function signature um it's
actually not that complex once you kind
of get past the scary syntax um but but
don't feel bad if you have to pause the
video and take a good look let's get our
hands dirty with this assignment says
texo is launching a new email messaging
product
maleo I guess uh SMS and email right um
fix the compile time bug in the get
formatted messages function the function
body is correct but the function
signature is not okay so this formatter
function here looks like it will be a
problem let's go ahead and run the code
and see what kind of an error we get so
this is a compile time error uh saying
we have a syntax problem and that makes
sense because here we're we're basically
saying well the formatter input is a
function but we're not saying what type
of function it is and that doesn't work
because get formatted messages is going
to use the formatter function so it
needs to know essentially what
parameters it takes as input and what
it's going to return right uh kind of
like uh the arithmetic function here
takes two integers and returns an
integer uh we need to update this
function signature to kind of inform the
get formatted messages function what
this formatter function actually does so
if we look at the code because we know
the function body um is correct it looks
like formatter accepts a messages input
which is a
string and it's going to return whatever
should be appended to this formatted
messages slice which is a string so it
it looks like it takes one string as
input and and returns one string as
output that kind of makes sense for a
formatter function um so we say takes a
string and it returns a
string let's go ahead and run that and
see what we get cool at least it
compiled says thanks for getting back to
me which return thanks for getting back
to me kind
regards Okay cool so the ad signature
ads kinds to the end and add greeting
thanks for getting back to me hello
thanks for getting back to me cool let's
go ahead and run
that awesome so this might seem like an
exercise in complexity right like why do
I need to pass around functions as data
that just seems to add a bunch of well
needless complexity and for the most
part you're right you really should only
use higher order and first class
functions if you have a very good reason
to do so so the question is kind of what
are the good reasons to do so well first
class and higher order functions are
very often used for um on the back end
side of the stack HTTP handlers right so
if you have some front-end code that
needs to reach out to a backend server
and get some data those handlers are
typically first class or higher order
functions because we need some code
right the Handler function to run like
in the future we don't want to call it
now right we want to call it when
something happens in the UI World kind
of on the on the front end side of the
stack uh they're often used in onclick
handlers right so I write a function and
I don't necessarily call it when my
program starts but I call it when
something interesting happens like the
user clicks a button right when a button
click happens I want to call you know
this function and so typically in code
we can represent that with a higher
order function we say onclick do this
and then we kind of give it the name of
a function to call and then just to
review the definitions uh very quickly a
first class function is a function that
is kind of being passed around as data
and a higher order function is the
function that's using that first class
function right it's a function that
accepts a function as input or returns
it um as one of its return values so the
question is what is a higher order
function um it is a function that takes
another function as an argument or or
returns a function um but it's not a
function that is first in the call stack
or a function with Superior
logic so the next question is what is a
first class function um it is a function
that is treated like any other variable
right it's a function that we're going
to pass around as as data to probably be
called sometime in the future function
currying is kind of like a special kind
of higher order function it's a function
that accepts another function as input
we're kind of familiar with that idea
but that also returns a new function as
its output so it's kind of a way of like
enhancing a function with new Behavior
it's kind of a weird concept to think
about abstractly so let's jump into this
example okay so here we have a function
called self math and self math is the
curried function it takes a math
function as input that accepts two
integers and returns an integer and it
returns a new function that only takes a
single integer as input and returns an
integer and then what it does again it
returns a function right that takes a
single integer an input and returns an
integer and it calls the math function
that it was given with the same input
twice right so we're kind of mapping a
function that accepts two uh different
integers into a function that kind of
forces both of those integers to be the
same integer so like what does that do
in practice well basically we can use
our self math function to convert a
multiply function into a square function
right so multiply takes X and Y and
multiplies them together this new Square
function that we created dynamically
only takes a single value and multiplies
it by itself right um same with the add
function we can kind of use the self
maath function to convert it into a
function that just doubles its input
right so we Square Five we get 25 we
double five and we get 10 so when would
currying be used in the real world uh to
be honest I don't use it very often um
but in backend server land I do
sometimes use it for middleware
functions so a middleware function is a
function that basically changes the HTTP
Handler of a backend server um and just
as a spoiler we will be covering this in
the project at the end of this course um
but it's for kind of injecting some
additional logic into a function so say
we have an HTTP Handler that accepts as
input a user ID and returns an entire
user object with you know say their
first name their last name that sort of
thing
um a middleware function might do
something like require an authentication
token right so we can write all of our
HTTP handlers that serve different data
sort of independently and then we can
use a curried function to kind of
require authentication Logic on all of
our HTTP handlers um in fact in go
currying is very often used to handle
the sort of middleware problem now if
all of that went straight over your head
again that's okay it's kind of hard to
talk about um something that we're not
working on at the moment so again we're
going to cover middleware and HTTP
handlers in the project at the end of
this course so stick around for that so
jumping into the assignment it says the
maleo API needs a very robust error
logging system so we can see when things
are going arai in the back end we need a
function that can create a custom logger
a function that prints to the console
given a specific formatter Okay cool so
this function get logger should return a
logger which is a function right it's a
function that takes two strings as input
and apparently prints them because it
doesn't return anything right and get
logger takes as input a formatter which
accepts two strings as input and returns
a single
string okay so it says complete the get
logger function it should return a new
function um the inputs should be passed
into the format or function the order
they given to the loger function okay
so let's start writing this so we're
going to return a
function that takes two strings as input
right and I'm going to reference up here
for the syntax we're interested in so
I'm going to do a
string B string all right and this
function should return nothing so we can
just go straight into the
body and it
says this function prints the formatted
inputs so formatter returns a single
string so we can just do fmt do print
line and we're going to need to oh we
already have fmt imported perfect so we
need to print the result of the
formatter functions it's going to be
formatter and we'll pass into the format
A and
B right so we're returning a new
function a logger that accepts two
inputs formats them given the formatter
that we were given right and then just
prints it to the console cool let's go
ahead and run
that error on database server out of
memory error on database server CPU is
pegged this all looks good let's see how
the test site actually
works it looks like these are the
formatters colon delimit and comma
delimit and if you take a look at our
messages some are delimited by a colon
some are delimited by a
comma so that's how that is working very
cool all right I'm going to go ahead and
submit that I think this is
correct time to talk about the defer
keyword uh this is a really kind of
unique thing to go um if you're familiar
with any other programming languages
it's unlikely um that you're familiar
with something similar to the defer
keyword um at least I've never used it
or used a concept uh that is similar to
the defer keyword in another language um
fairly go specific
okay so the defer keyword allows us to
execute some function at the end of the
current function or when the current
function exits the defer keyword is very
often used as sort of a cleanup step um
for example in this in this function
here copy
file we open a source file from the file
system and then we defer closing the
file right so every time you open access
to a file in the file system you need to
remember to close that file otherwise
you're sort of wasting Computer
Resources um the problem is closing the
file at the end of the function is a
little tedious because we have multiple
return statements here so we'd kind of
have to close it before both of them if
that makes sense by using the defer
keyword we can just tell the go
programming language hey I want to call
source.
close right before the copy file
function ends and just defers it kind of
until the end and it will only call it
once um no matter where the function
actually returns from on to the
assignment it says there is a bug in the
log and delete function fix it let me
expand this a little bit so we can see
it this function should always delete
the user from the users map Okay cool so
we're given a user's map we're given a
name and we know that Maps uh are passed
by reference so if we delete a map uh it
will be deleted in the callers code as
well um it should return log string that
indicates to the caller some information
about the user's deletion okay so delete
should always happen and then the kind
of the appropriate log message should be
returned from the function that makes
sense um but it looks like there's a bug
okay so let's go ahead and run the code
and see what we
get so initial users Brianna Elon John
Cade attempting to delete John deleting
Santa deleting
Cade okay so John's still there the
problem is that we're trying to delete
JN but Jon's not actually getting
deleted right so we need to fix that
bug and if we take a look John is an
admin and here we are returning log
admin but we're not deleting admins so
so that's that's the real problem right
the users should always be
deleted now here's the problem we can do
this um this will work if I run this my
guess is this should fix the bug yep JN
is no longer there he was deleted
successfully but this is kind of gross
right we we're calling delete three
different times um what if in the future
we add another case and we forget we
forget to add the delete again we'll
have another bug um well what we could
do what we could do is just delete at
the top
once
right but the problem is if we do
that we delete the user from the users
map too soon and now this okay variable
that's checking for existence so it can
change which log is returned like that
logic won't work it will just always be
not okay so we'll always just return log
not found so that's a problem what we
can do is defer the deletion so this
code says we'll call this delete
function right before login delete
returns so it it's almost like um the
equivalent of you know adding this at
every step of the way so let's go ahead
and run that make sure it
works John's there John's gone perfect
and we have different logs getting
returned so that looks good I'm going to
go ahead and submit that this chapter is
not called Advanced functions uh for no
reason let's talk about closures
closures are um I don't want to like
intimidate you and say they're really
hard but they're a little weird so let's
let's take a second to understand uh how
they work okay a closure is a function
that references variables from outside
its own function body the function May
access and assign to the referenced
variables so in this example the concat
function returns a function that has a
reference to the enclosed doc value okay
so concat is a function it returns a new
function right and that's what's
happening
here and it's enclosed using this doc
string within the function so it's
getting initialized outside of the
function and then getting used within
the function and what happens is
basically when we call concat to to make
this new like concat
function we're saving a reference to
this doc variable so every kind of
concurrent not concurrent every uh
subsequent call to this function that is
returned we'll keep adding on right plus
equals word we'll keep adding on to the
same doc variable so let's look at what
that uh kind of looks like in usage so
we're calling concat and we're getting
back this Harry Potter aggregator right
that's what we're assigning this
function we're assigning this function
into this variable so Harry Potter
aggregator is a function right it's it's
specifically it's this function right
here cool and then we're going to call
it with mis and then we'll call it again
with and and Mrs dersley Right we're
calling it over and over and over and
what's happening under the hood is we
are appending those words Mr and Mrs
onto that doc variable so at the end
when we finally
print what's being returned which is the
doc variable itself we get the full
sentence Mr and Mrs Durley of number
four privet drive now closures are one
of those Advanced things that you to be
honest again don't usually use in
production code maybe every once in a
while um but they're very important to
understand because it is very common to
have kind of bugs surrounding closures
so if you don't understand what's going
on under the hood it can be really hard
to debug kind of complex code that might
even be using closures by accident so
the assignment says keeping track of how
many emails we send is Mission critical
at maleo complete the adder function
it's return a function that adds its
input an integer to an enclosed sum
value and then Returns the new sum in
other words it keeps a running total of
the sum variable uh within a closure
Okay cool so it's going to be very
similar to this right so we'll start by
creating a sum value so sum colon equals
zero right we're we're working with
integers not flows and then we need to
return a function with the signature so
return Funk X int returns an
INT oh not not returns an X returns an
INT and then here we need to add X to
sum right so sum plus equals
X and then we need to
return the new
sum right and then if we go take a look
at this test case let's
see okay so we're adding these email
bills with a bunch of different
numbers the test Ates over the bills ah
okay so we're creating two adders here
so we're creating a count adder and a
cost
adder and then let me expand
this going to just make that basically
full
screen so the test actually creates two
different adders two different instances
of our Adder function right and one of
them is going to count how many bills
there are and the other one will keep a
running total of the cost of all of the
builds
right so we're actually using our
function twice for two different things
and they'll each have their own enclosed
some count that they keep track of
differently right the count will have
its own the cost will have its own the
count is simple it just adds one every
time the cost will add the actual cost
in pennies cool let's go ahead and run
this so um and these values are getting
interpolated into this message so youve
sent one email cost 45 cents two emails
77 cents three emails 120 cents so like
all of these are going up that makes
sense I mean we could even go verify
right 45 + 32 77 that looks right to me
let's go ahead and submit it all right
we're on to a little quiz about closures
can a closure mutate a variable outside
of its body uh yes that's basically the
entire point of a closure another little
review question here says when a
variable is enclosed in a closure the
enclosing function has access to blank a
copy of the value or a mutable reference
to the original value well if it was a
copy then our sum never would have
worked right because we'd be working
with a new copy of the sum variable
every time so it's actually a mutable
reference to the original value we've
already been using Anonymous functions
kind of all throughout this chapter um
now let's kind of just talk about what
they are so Anonymous functions are
exactly what they sound like they're
just functions that do not have a name
they have no name um Anonymous functions
are really useful when you're kind of
just oneoff maybe creating a closure
you're returning a new type of function
um if if you're not defining the
function for like use across the entire
program but you're more using it as a
value using it as a first class function
um that's when you're going to see
Anonymous functions use the most so as
an example um here we have an anonymous
function declaration right we're
creating a new function we're defining
its internal logic it's its function
body um but we're not giving it a name
right there's no name uh for this
function that we're passing into do math
it is an anonymous function the
assignment says complete the print
reports function call Print cost report
once for each message okay let's take a
look print cost report looks like it
takes as input a function cost
calculator great okay um call Print cost
report once for each message pass an
anonymous function as the cost
calculator that returns an int equal to
twice the length of the input message
okay let's go ahead and do that
so um it says to do it once for each
message yeah so
four
message range
messages call Print cost report print
cost
report and print cost report takes a
cost calculator which is a function that
takes us string and returns an
INT said to use an anonymous function
right so funks we need to actually
Define our X string int we need to
define the body um that returns an INT
equal to twice the length of the input
message so X is a string in fact I'm
going to name this um message and we'll
return the length of
message time
2 right twice the length of the input
message and then print cost report takes
us second parameter which is the message
itself
right I think am I am I reading this
correctly cost report FES the calculator
and the message itself yep because it's
going to it's then going to call the
cost calculator and then and then print
kind of a little report okay cool um
that's looking pretty good to me we
don't return anything from print reports
okay let's go ahead and run this
so message here's Johnny cost 28 cents
go ahead make my day 42 cents you had me
at hello 38 cents that's looking correct
right there's
19
28 not 19 what's half of 28 14 14
characters there looks good to me okay
let's go ahead and submit
that let's talk about pointers and in
order to understand pointers we need to
um talk a little bit about Ram or memory
right Random Access memory which is
basically the part of the hardware in
our computer that stores data right
because pointers and variables are all
about kind of how we store data in the
memory of our computer let's start with
something
simple let's create a new variable in
our
program and let's call it X and we'll
set x equal to
5 under the hood what happens
automatically when we create a new
variable uh and set it equal to five is
somewhere in our computer's memory that
variable's value is going to get stored
so let's say uh for the sake of the
example that it's stored here in memory
address 169 Now Memory in your computer
you could think of it as fairly simply a
memory address that stores a value so
we've got kind of you know millions of
different memory addresses in which we
can store data and somewhere in memory
uh that value needs to live so let's
just say it gets assigned again automat
aut atically um to address 169 great now
what happens if I create a new variable
let's call it Y and set it equal to the
current value of
x y actually gets a new copy of the
value so X lives here at address 169 y
let's just say is going to live at
address 170 and we get a copy of that
five so down here in this table this is
where we're going to keep track of all
of our variables we'll say that X so X
is the variable name lives at address
169 and it stores the value
five now
y lives at address
170 and stores also the value five at
its most basic a pointer is just a
variable that stores a memory address so
let's say for example that we create a
new variable called
Z and we set it to point which is uh
which uses the Ampersand I can't draw an
ampersand pretend that that's an
ampersand uh the the the a reference to
X right it points to x what that does is
that we're creating a new variable
called
Z it's going to get a new address
anytime we create a new variable it's
going to live in a new address in memory
let's say it lives at address
171 but its value instead of being five
instead of being a copy of X because we
used kind of the pointer syntax the
reference
syntax the value is going to be the
address of
X so we're going to
store 169 which is the again the address
of X as the value and Z is going to be a
pointer type so kind of the way the go
programming language works works is it
knows that Z is effectively pointing to
the value five right because its address
is
171 which stores a pointer to 169 so we
can look up the value five at address
169 so what this means is if we update
the value that Z points to under the
hood we're updating X so we can do
things like pass pointers into functions
change the underlying value and the
value outside of that function is also
changed which again in a language like
go doesn't happen normally because
typically if you pass a variable like X
into a function and then change it those
changes are not seen outside of the
function you would need to return the
updated value and then assign it into a
new variable so pointers basically allow
us to change the value of something from
within a function right from within a
different scope let me show you what
that looks like in a quick example so
remember that Z stores the value
169 which points to this
address which stores the value
five so if we want to update the value
of x without having access to the
original variable x what we can do is
use the dfference operator which is an
asterisk we can dreference Z now this
dfference operator essentially follows
this chain and finds the value right and
we reassign into it let's say the value
six so this becomes six and this
location and memory is updated now to
hold the value six so we've updated the
value of x without even without even
using right the name X so now that we've
kind of covered what a pointer is let's
look at just a little bit more of the
syntax in code so the type of a pointer
is not the type of the underlying value
alone so if I want a pointer to an
integer um I actually have to use this
syntax here I'm creating a new variable
called p and it's being initialized as a
pointer to an integer so a pointer is a
specific type in go now to be fair um
it's not super common that you're
creating blank pointers and the zero
value for a pointer is nil um more often
than not what you're going to do is have
a concrete value like this my string
hello and then you'll create a pointer
to that value by using the Ampersand
like we talked about so in this case the
type of my string is string and the type
of my string pointer is a pointer to a
string which would be syntactically star
string there kind of two primary reasons
that you would use a pointer in the go
programming language the first is the
more common reason which is you want to
be able to pass a value into a function
and change the value and have those
changes kind of persist outside of the
function right because normally when you
pass a value into a function a copy is
passed in so if you want to pass in sort
of the original value so it can be
changed and updated uh you might use a
pointer the second reason is if you're
very concerned about the performance of
your program every time you create a
copy of a variable in memory you have to
copy that variable in memory right which
takes some time so if you're dealing
with lots and lots of data and you're
trying to be very performant you can
make micro optimizations and kind of use
pointers under the hood if you want to
avoid all of that memory copying now I
will say that you usually won't want to
do this upfront because pointers are
dangerous and they can lead to bugs if
not used properly so generally speaking
I would recommend against making those
performance optimizations unless you
really need them let's jump into the
assignment says fix the bug in the send
message function it's supposed to print
a nicely formatted message to the
console containing an sms's uh recipient
and message body however it's not
working as expected okay so let's run
the
code and we get these kind of weird
weird looking values that are getting
printed here in the two and the message
Fields now this is heximal it's not
binary it's not decimal it's hexadecimal
and this is the default formatting for a
memory address so we have these amp
perents here they're creating pointers
to the underlying values that's not what
we want it's not what we want here right
that's why this looks disgusting uh we
need to dreference or or sorry we need
to remove the reference so I guess D
reference
um we need to remove the amperian so
that we are no longer creating pointers
so run it again and we get the values
themselves which is what what we've been
asked to do
let's recap some syntax sometimes it can
get a little confusing between the
asterisk and the Ampersand what each of
them mean and in what context so when
we're talking about the type of a
variable in go a Pointer's type is star
and then you know the type of the
underlying value so a pointer to an INT
is star int an ampersand is used to
reference a value so if we want to
create a pointer to the my string
variable or the my string value then we
use an ampersand so the Ampersand
followed by the name of a variable
creates a pointer to that variable now
here's where it can get just a little
bit tricky the asterisk is again used to
dreference a pointer so the asterisk is
used in a Pointer's type it's also used
as an operator to dreference a pointer
so when we say say asterisk my string
pointer this refers to the underlying
value so we can actually change the
underlying value Say by assigning it to
in this case a new string so in short
ampersands to create new references or
new pointers to a value and the asterisk
is used to dreference a pointer and get
back at that underlying value let's hop
into the assignment it says complete the
remove profanity function it should use
the strings. replace all function so
this is a a built-in uh function in the
standard library in the strings package
uh to replace all instances of the
following words in the input message
with asterisks it should mutate the
value in the pointer and return nothing
do not alter the function signature Okay
cool so remove profanity takes as input
a a a a message variable which is a
pointer to a string so because it's a
pointer we're going to be able to mutate
it without returning anything explicitly
okay let's jump into it so first thing
we're going to do is dreference
the message pointer and store its value
in a new variable called message value
and then we'll just update
this um by using that strings. replace
all functions strings. replace
all we want to replace the values in
message value we want to or I should say
we want to replace subrings from message
value uh we'll look for the word dang
and we'll replace it with four
asterisks and we want to do the same
thing twice more with shoot which has
five letters so let's add an asterisk
and heck which has four so that should
be good cool let's let's run this I need
to import the strings package before I
forget and see what happens
so in its current state we actually did
not update anything right shoot is still
there dang is still there that's because
we actually need
to point the
message pointer to a string back at the
updated value so we can do dereferenced
message
equals message value let's run
that perfect let's submit it
now we've got a pointer quiz so the
question is what is the value of
Ampersand y after the code on the left
executes so we've got X set to 50 Y is a
pointer to an
integer okay well in this case it's just
going to be 100 because we're explicitly
setting it to 100 on the last line of
the code so that one was pretty easy now
this question is a little trickier says
what is the value of x after the code on
the left executes
well X is set to
50 Y is a pointer to X and then we
dreference y instead of equal to 100 so
that's actually also going to be 100
because we're setting X through Y which
is a pointer to X I mentioned earlier
that pointers can be dangerous and that
is definitely the case um if a pointer
points to nothing then its zero value is
nil right so this is the same for
interfaces or errors right in go fairly
often you'll be checking at runtime if a
value is nil or not and the thing about
pointers is if you ever try to
dreference a pointer that doesn't point
to anything your code will Panic so
pretty much any time you dreference
something you should be checking before
you dreference it to make sure that the
pointer actually points to a valid
location in memory so this assignment
says let's make our Prof profanity
Checker safe update the remove profanity
function If the message is nil return
early to avoid a panic okay so let me
run it in its current state and you'll
see we get this nasty Panic here it says
invalid memory address or
nil okay so what we need to do here is
if
message equals equals
nil return nothing right if we've been
given an invalid input we'll just bail
early um another way to do this um if
the assignment expected something
different we could return an error here
right we might do something like return
errors. new uh invalid input I think
that would also be a good way to write
this function uh but we've been asked to
just do a naked return so we will do
that let's submit it so let's talk about
how pointers are used in conjunction
with methods so you'll very frequently
see that methods actually take a pointer
receip
rather than a non-pointer receiver and
typically that's done because the method
will be making changes to the instance
of the type itself in this case a struct
right so we have this car with a color
field and the set color method on the
car is going to change the color right
and so here if we create a new car we
set the color to blue and then print it
you can see it's been updated to Blue
instead of white now contrast that with
a non-pointer receiver
this acts like a normal function right
we don't have a pointer so we don't have
a reference to the location in memory so
if we update the cars color to Blue it
actually just stays white it doesn't
persist that change so that's why I say
that it's uh more common that you'll see
pointer receivers on methods than
non-pointer receivers but but you will
definitely see both so uh the question
is which is more widely used in go and
the answer is pointer receivers now when
it comes to pointer to receivers one
thing that's important to understand
from like a syntactic point of view is
that even though the uh the input on the
left hand side right the receiver is a
pointer when you actually call the
method you can call in this case right
the grow method you can actually call it
on just a normal value or a pointer it
will sort of cast the value under the
hood to a pointer if it isn't one
already right so C in this case is just
a circle it's not a pointer to a circle
but when we call c. grow the pointer to
the circle is passed into the method so
we didn't need to kind of explicitly you
know cast c to a pointer to a circle by
you know adding an Amper sand right here
okay so with that understood um let's
move on to the assignment so the
assignment says fix the bug and the
codes that set message sets the message
field of the given email structure and
the new value persists outside the scope
of the set message method Okay so we've
got this email struct we've got the set
message uh
the set message method if I run the code
in its current
state we've got before message this my
first draft Sandra Bullock after looks
identical right and if we go and take a
look at the test Suite then we can
see
that set message is being called this is
my second draft so we would have
expected this message to say this is my
second draft instead of this is my my
first draft the reason it's not
happening is because this function
essentially doesn't do anything this
method does nothing because this is not
a pointer to an email let's go ahead and
run that
again first draft second draft perfect
let's submit
that everything we've done up to this
point in the course has been in the
browser we've been writing code right in
our text editor on boot Dev now we're
going to break out of that environment
and do some local development on our own
machine we're going to figure out how to
use the go tool chain to build real
production go
programs but before we do that let's
talk a little bit about packages we need
to understand packages in order to build
our own go programs now you've probably
noticed that up until this point in the
course every coding assignment has had
the words package main at the top of the
file that just means that we've been
writing code within the main
package and that's actually really
important the main package is a special
type of package it's a package that runs
as a standalone program so anytime
you're writing an actual application
that you need to run say on the command
line or as a server you'll be writing
code within the main package a package
with any other name so any name besides
main is a library package and basically
what that means is it's imported by
other libraries and application code so
some oftentimes it's just imported by a
main package so that it can use it if
you're familiar with npm from JavaScript
or pip from python that's effectively
what a non-main package is in go it's
just some code that you can publish as
kind of a standalone library that then
other developers can use in their main
packages in their actual applications so
let's take a look at an example here so
you're probably familiar with this style
of code it's what we've been writing uh
all throughout this course we've been
writing within the main package because
we've been writing executable scripts
right code that actually runs and does
something and then we have this import
statement where we've typically just
been importing from the standard library
but the standard library is made up of
Library packages right like the fmt
package or the math SL random package
the interesting thing about the main
package is it always has a main function
and that main function serves as the
entry point to the program so packages
that are not main packages their Library
packages will just export functions
named functions to be used Again by
other libraries and application code
only main packages will have a main
function that runs when the program
starts so to jump into the coding
assignment it just says fix the bug in
the code so let's go ahead and run the
code and see what happens
nothing happens right we're stuck in
this infinite Loop apparently apparently
this code compiles and then does nothing
it's kind of a weird Behavior right um
in order to fix this all we need to do
is update
this to be a main package so that we get
as output for our code a runnable
executable a script that we can actually
execute in the browser so let's go ahead
and run that and we get starting maleo
server stopping maleo server kind of as
we would expect uh based on uh based on
this code here so I'm going to go ahead
and submit that so we're familiar with
main packages but what about Library
packages well by convention a package's
name is the same as the last element of
its import path so for example the math
Rand or math SL Rand package from the
ghost standard Library um has files that
begin with package Rand so the Rand
package lives at math SL Rand now it's
important to point out that the standard
Library actually has another Rand
package at crypto Rand so they're both
kind of the Rand package but they have
different import paths when a package
isn't part of the standard Library when
for example you make your own Library
package the import path is typically the
same as the remote URL that you you'd
use to go look at that library's source
code so for example in our fictitious
maleo products right part of the textio
conglomerate of messaging products uh
they might have their own GitHub
namespace it's slmo so github.com is
their GitHub organization and then maybe
they create their own package or their
own excuse me repository on GitHub
called Rand and that's where the
repository for their Rand package lives
that's where the source code lives on
the internet right github.com
Rand now that package's name by
convention should be Rand because that's
the last section of the import path but
that's just a convention they could if
they wanted to change the name of the
package to say random and then all of
the files in their source code would say
package random now again it is possible
to use a different package name one
that's not the same as the last section
of the import path but I highly
discourage it um it is best practice to
just use the convention and keep
everything consistent now one last thing
to point out here is that in go packages
live at the directory level rather than
the file level so if you're used to say
python or JavaScript you might be used
to importing code directly from other
files in go if all of the code lives
within the same directory then it's part
of the same package and you don't need
to Import and Export code between files
in the same package or in the same
directory so in go you would only need
to import code if it lives in a
different directory or a different
package and packages and directories are
one and the same you can't have multiple
packages in the same directory so the
quiz question for this section is what
would be the conventional package name
of a package with the path github.com
waglan parser and the answer is going to
be parer because it should match the
last section of the import path the next
question on the topic of package naming
is given the import path of path to Rand
path2 Rand which of these is a valid
package name so notice it's not asking
which is a conventional it's asking
which one is actually valid we've got
random Rand spam any of these path well
Rand would be the conventional name but
actually any of these could technically
be used okay it's time to write some go
code on our local machines and you're
going to need three things in order to
make it through the rest of this chapter
the first thing is you're going to need
an editor I'll be using VSS code
throughout the rest of this chapter to
do all of our local development stuff
you can use vs code or you can use
something else uh that you're more
familiar with if you do use vs code
you'll probably want to install the
official goang plug-in the next thing
you'll need is a Unix like or sh like
terminal so if you're on Mac or Linux
then the built-in terminal will work
just fine if you're on Windows you have
a few different options personally I
would recommend installing Ubuntu in
wsl2 I'll link uh in the description
down below how to do that uh but you
will need to have a terminal in order to
follow along with these instructions if
you choose to use something like the
default Windows command line you're
going to have to change some of the
instructions in the terminal uh to kind
of translate them to your own machine
the last thing you'll need is just to
install the go tool chain on your local
machine and I would recommend one of two
different ways the first is to use uh
the official download page and the
second is to use um the Webby download
script either of them will work and I've
linked them both on this page so you can
go check them out and get go downloaded
now once you've downloaded and installed
go the way you can test to make sure
that everything is working correctly is
type Go version in your terminal if you
get back a valid Go version of at least
version
1.20 which is what I'm using at the time
of recording this video uh then you
should be good to go now I need to point
out at this point that some people get
confused and think this is Go version
1.2 it's not it's Go version 1.20 which
is the same as Go version 1.20 which is
one version greater than go 1.19 or go
1.19 so make sure that you're not on Go
1.2 that would be very very old if
you're having trouble getting go
installed so that you can use it within
your terminal uh the first thing I'll
have you do is definitely to try closing
your shell session and restarting it um
a lot of times that can help just
getting go into your path uh if that
doesn't work then Googling around uh C
can can be one method to kind of try to
figure out what's going on on your
machine it's really hard for me to kind
of predict what issues you might have
especially considering the fact that Mac
OS windows and Linux can all be a little
different but what I will recommend is
that if you are having trouble jump into
the bootd Discord and ask a specific
question about the trouble you're having
and we'll be happy to help you out so
the question for this step really just
to confirm that we installed go
correctly and that we can use it uh asks
what does the Go version command print
and if we check the output we can see it
says Go version go 1.20 so that's the
that's the version of the go tool chain
we have uh Darwin I'm on a Mac so this
is like the core Unix operating system
and then amd64 which is my CPU
architecture so it's going to be this
bottom one so we've talked about
packages and we've talked about how
packages exist basically at the
directory level and go now we need to
talk about modules a module is a bigger
idea than just a package a module is
kind of a releasable collection of go
packages so sometimes it's just a single
package right you have a single package
a single module and you release it um as
one package but other times you might
break that main package up into sub
packages and you'd release all of those
packages together as one go module now a
repository is not a unique idea to go
but it's sort of even greater than just
a module repository or a git repository
is just a collection of code that you
kind of keep in Source control all at
the same level and it can contain one or
more modules though it's actually really
common for a single repository to just
have a single go module with within it
so while one monor repo can have many
different go modules inside of it I
would argue that's not usually the case
typically you'll have one go module
living in one repository that's not a
convention that's just the way I've seen
it more often than not so for the sake
of example let's assume a git repository
that contains a single module and that
means that at the root of the git
repository we'd have a single go dood
file that looks something like this the
first line contains the path prefix for
the entire module so remember a module
can contain multiple packages and every
package in that module its import path
is going to be prefixed with the path of
the entire module uh the next line just
specifies the Go version um and the last
line will specify any dependencies that
this module depends on so to answer the
question what is a go module an
executable main package um no that could
be part of a go module but it's not a go
module
itself a collection of packages that are
released together that is accurate a
file of go code no a module will contain
many many files of go code um or a
library package and again no not
necessarily you could have a module
that's just a single Library package but
a go module can be many more packages
than that or it could be a main package
for example so it's going to be a
collection of packages that are released
together so we need to talk just a
little bit more about these import paths
now if you're familiar with npm from the
JavaScript e ecosystem uh cargo from the
rust ecosystem or pip from the python
ecosystem then you're probably familiar
with the idea of a namespace for a third
party package for example I might make a
package called Left pad publish it up to
npms uh Central repository and now
anyone in the world just knowing my kind
of namespace name uh leftpad can
download use my code as a dependency the
interesting thing about the go ecosystem
is that there is no central location for
thirdparty packages like npmjs.com in
JavaScript land instead the gotool chain
sort of works on top of git and actually
uses the import path as the remote URL
and it looks to that import path for
where it can go download code so more
often than not you'll see modules with
import paths that start with github.com
or gitlab.com because that's where the
code itself is hosted an important
exception to this rule that the import
path is where you go to download the
code is the standard Library you don't
need to download standard Library code
it comes packaged with the go tool chain
so the question is do packages in the
standard Library have a module path
prefix the answer is
no the next question is what is an
import path and I know we've already
talked about this but I to show you a
more concrete example so this is the
github.com
waglan go- rabbitmq GitHub repository so
this is a GitHub repository that I
maintain it's just a little package for
Rabbid mq users a little go client for
Rabbid mq so this is a library module
and a library package it's intended for
other developers to actually import this
code into their applications it's not a
standalone you know main package so
let's take a look at the file structure
at the very root of the repository we
have the go dood file now this is that
kind of simple standard setup where one
git repository happens to have one go
modable or one sort of releasable unit
of code and if we take a peek inside we
can see that the go dood file uh
specifies the import path github.com
waglan go- rabbitmq Q so this matches
the URL of the git repository hosted on
GitHub now here's the interesting thing
this module only has one package that's
exported to the outside world and it
exists here at this top level so you can
see all of these go files consume. Go
consumer options. go they exist at the
top level of the repository so when you
import this package to use it you just
import from this root path because the
root package is what you're importing
there are no sub packages to import here
but let's pretend for a second that we
did want to export another package in
the same go module so we have our top
level go module G rabbit mq and the top
level package that can be accessed at
the same import path as the module let's
say we added another directory we could
call it maybe networks and inside it
would be the networks package if someone
wanted to import that package then they
would take the import path for the
entire module github.com
waglan rabbitmq which points to the root
package like we already talked about and
it would just append another slash
networks onto the end right so the
module path serves as a prefix for any
nested packages so to answer the
original question what is an import path
well it's not an HTTP connection and
it's not a restful server it's a module
path
plus an optional package subdirectory so
we've talked about repositories modules
packages you've got go installed now
let's talk about how you can set up your
local development environment and some
of this stuff isn't going to be
necessary and I'll point that out but I
do want to show you how I've set up my
machine um and you can emulate the way I
do it if you like that style now I need
to mention the go paath if you're
Googling around trying to figure out how
to set up your local development
environment for go you will almost
certainly come across old outdated
articles that talk about the go path in
kind of any version newer than I think
it's 1.13 right so we're on
1.20 now um you can basically forget
about the go path you don't need to
worry about it um you used to have to
put your code in the go path um now the
recommended way is actually to not put
your code in the go path can be a little
confusing but again generally speaking
you can just forget about the go path
and if you find any articles that talk
about it they're probably outdated the
entire idea of go modules is what
replaced the old go path so the question
is do you need to put your code inside
of your go path um the answers are yes
it doesn't matter and no in fact you
shouldn't and actually in fact you
[Music]
shouldn't so I've altered my window
layout um so that's a little bit easier
for you to see what I'm doing I've got
the boot Dev instructions over here on
the left and then I've got vs code over
here on the right with my terminal at
the bottom cool so I'm just here in my
file system I'm not within the go path
I'm just kind of in my personal
workspace um and we'll just follow these
instructions it says create a new
directory and enter it so we'll make
directory hello
go and me just LS show you the new
directory was created and we'll enter
that directory cool now inside of that
new hello go directory I'm going to
create a new go module so we'll do go
mod in it
and I keep all of my code on GitHub so
I'll do github.com
SL my username because that's my
namespace on GitHub so Wags Lane slash
the name of this project which uh we're
just calling hello go cool says go
creating new module um now if I LS I
should see that new go.mod file next
let's go ahead and take a look at that
file so you can see it just created a
new module
github.com wax laneel go with the Go
version and there are no dependencies
yet so there's no other sections to the
go module file so now that we're done
with this step the question is why does
go include a remote URL in module paths
right so this is a remote URL in theory
right when we push this up to GitHub
we'll push it up to this URL um and the
answ are to confuse new Gophers uh to
ensure that developers are using Source
control or to simplify remote
downloading of packages and it is
definitely that last one the next
question on the same step asks what is
Hello go in our case the repository SL
directory name or the module path prefix
and in our case it's actually the name
of the repository so again if we were to
push this up to GitHub um GitHub is the
name of the website where it's hosted
wag Lane is my namespace on GitHub and
hello go would be the name of the
directory that we are in and the name of
the repository up on GitHub
then this next question is just to make
sure that we completed the step properly
what does the first line of go dood
contain and it contains module followed
by the module path now so that VSS code
will work properly with all my syntax
highlighting with the go plugin I'm
actually going to reopen uh vs code to
that same directory the hello go
directory so we'll just be working
within this module uh for a
second and the instructions say inside
of hello go so now I'm inside of this
hello go directory at kind of the top
level of uh my vs code window um it says
create a new file called main.go so
let's go ahead and do
that conventionally the file in the main
package uh that contains the main
function is called main.go uh paste the
following code into your file Okay cool
so we should all be familiar with this
code by now right we've we've probably
written code like this uh I don't know
almost a hundred times by now in the
boot Dev platform next it looks like
we're just going to run the code using
the go tool chains interpreter so gun
main.go
and we get a nice hello world printed to
the console there's sort of two main
ways to run go code locally one is with
the G run command like we just used the
other is to use the go build command
which we'll get into in just a second
and actually in general I prefer the go
build method uh G run is really only
suitable for when you're running uh tiny
little scripts like this one and just to
be clear about the difference G run so
gun main.go runs the go code in the file
that we pass in right in this case
main.go and and that's all it does go
build actually builds a production
executable um which is how you'll use
your go code in the real world so I
typically prefer building that
production executable um just because
it's more accurately reflects how your
code will run in production so the
question on this step is does g run
build a production executable and the
answer is no it just kind of oneoff runs
your code it doesn't build any uh
production ready artifacts now at the
end of this same step we were asked to
execute the go help run command in our
Shelf so go help run um which just
prints out a bunch of help information
on how to use uh the G run command and
the question for the step says uh which
can go run except as arguments if we
scroll to the top so here's where I ran
go help run uh we can see the usage
information and it says run compiles and
runs the named main go package typically
the package is specified as a list of.go
source files from a single directory but
it can also be an import path file
system path or pattern matching a single
known package so which can go run accept
as arguments package names file names um
it actually looks like it can handle
both great now we get to use go build so
you can for the most part just forget
about G run you probably don't need to
use it um all too often so go build
compiles go code into an executable
program so all you need to do is make
sure that you're in the same uh kind of
main package that you want to build so
I'm here in the hell go directory
um I can LS to prove it to you we've got
the main.go file here package main Funk
main all that good stuff uh we'll run go
build we don't even need to pass in
package or file names it just builds uh
the package of the current directory so
as you can see here we have this new
executable binary in our current helloo
directory now if you remember way back
to I think chapter one of this course we
talked about the difference between
interpreted languages and and compiled
languages and one of the amazing things
about go is that we build compiled
executables that we can execute anywhere
so this hello go binary we can now copy
this put it on another machine and run
it without even needing to install the
go tool chain on that machine this is
compiled machine code so we can now run
this hello go program like we would any
other executable /hello go and it prints
hello world now I do want to show you a
little tip here because every time you
update your code let's say I change this
to hello world 2 if I just rerun hello
go it still will just print hello world
and that's because I didn't recompile my
code so a lot of new go developers kind
of forget to recompile their code and
they think they have bugs or their
codee's not doing what they expect you
have to recompile what I'd recommend
doing is just every time you update your
code and want to rerun it uh you should
build a compile and you can just do it
in the same step uh by doing go build
and then double Amper sand do/ the name
of your binary so this basically says
compile my code if that succeeds run the
compiled binary and the question for
this step is what was created after
running go build an executable file
named main an executable file named
hello go or a package named CMD and in
this case it was an executable file
named hello go right we've got it right
there it just kind of defaults to the
name um of the directory the next
question for this step is what happens
when you run /hello go and I actually
changed this to hello world to but if we
do it again after changing the code back
um the options are program panics hello
world is printed or the code compiles um
hello world is printed and it's
important to understand here the reason
we don't say the code compiles is
because at this Steph Helo all we are
doing is running the compiled binary
we're not recompiling the code the next
command in the go tool chain that we're
going to talk about is go install now to
be honest I don't use it a ton but I
still think it's important to understand
because you'll see it around okay so Ure
you're in your hello repo then run go
install so I'm here in the hello
directory and we run go
install and next we navigate out of our
project directory so I'm going to go up
one
level and what it's telling me is go has
installed the hello go program globally
so the go tool chain when we ran go
install basically compiled that code and
then made it globally accessible to our
entire machine so now if I run hello go
notice without the dot slash because I'm
not running a program in my current
directory I'm running now a program
that's in my
path it prints hello world so now I can
use this script that I built anywhere on
my machine which is just super useful um
if you're building kind of scripts for
your own local productivity so the
question for this step is what does go
install do saves local code to the
remote Source control provider installs
dependencies or compiles and installs
the program locally and the answer is
that it compiles and installs the
program locally again you would really
only use this if you're building scripts
for your own personal use on your
machine the next question for this same
step is code must be compiled with go
build before running go install it's
true false now if you were paying
attention you probably noticed the
answer here but we actually ran go
andall
without a binary existing in our current
directory so if I go back into the hello
go directory and run go install you'll
see there's actually no hello go binary
that exists here um and it works just
the same so the codee does not need to
be compiled with go build
first all right so we've created a main
package and a executable program uh now
we're going to build a library package a
custom Library package so uh what we're
supposed to do is create a directory at
the same level as the hello go directory
so I'm in the hell go directory
currently I'm going to go up one level
and let's see so we've got helloo in
here I'm going to make a new directory
called my
strings and then we'll go into my
strings and here we need to initialize a
new go module so I'll do go mod in it
github.com Wags
Lane slash my
strings and that module is created now
to get my VSS code toing all working
properly again I'm going to just
reopen VSS code to that level so we'll
go back and find the
myrings directory and open directly into
that
directory
okay next we create a new file called
myrings Dogo so this is the go dood that
we created with the go mod in nit
command and now we're creating a new
file called myrings Dogo
and we want to paste the following
code okay so package my strings that's
the first thing to notice right this is
not a main package so we won't be able
to build this package as a standalone
executable this package will be kind of
the intention is for us to use it in our
other hello go package the other thing
to notice about this code aside from the
package name is that there is no main
function here again because it's not an
executable program we're just going to
be exporting functions that can be used
in our main package let's talk a little
bit about this reverse function so this
is the only function in the myrings
package at the moment and uh you know
theoretically it reverses a string from
left to right although I haven't tested
it um but the important thing is that we
need to capitalize we need to capitalize
the first letter of the function's name
and that's because in go that's how we
export a function if it were little r
then this function would not be able to
be used outside of this package but we
want to export it because we want to be
able to use it in our main package right
in the helloo directory um and and kind
of import this logic and use it now
we're told to run the go build command
here in this directory and you'll notice
it doesn't actually seem to do anything
there again there was no executable file
that was built because this is not a
main package um but it is worth pointing
out that go build is still a useful
command because it checks for compile
time errors so for example if I uh
create an invalid token here and then
run go build uh we will get compile time
errors so it is um still kind of useful
just to make sure that this code
compiles so the question for the step is
what was the output of the go build
command in the library package um was it
an executable program or was the
compiled package silently saved to the
local build cache well we didn't get an
executable package right uh this is not
a main package so we don't get um a nice
runnable executable instead it is
silently saved to the local build cache
so it can be used later in an actual
executable program and then the next
question is why is the function capital
r reverse instead of lowercase are
reverse and it's lowercase names aren't
exported for external use or
conventionally uppercase names are used
in go the answer is that lowercase names
are not exported so by convention
functions can have either a lowercase
name or an uppercase name and uppercase
names are exported from the package so
this next question is does a package in
a folder named date parser need to also
be called Date parser right does the
package need to be date parser now
you'll notice that we created a
directory called my strings and a
package called my strings and if you
remember back to earlier in this chapter
we talked about how by convention that's
the best way to do it it's not necessary
um but it is it is the convention okay
so now let's use our reverse function
back in the main package in the hello
directory right so it says um modify
helloo main.go so I need to
reopen I'm going to open vs code back
into the hello go
directory it says modify hello Go's
main.go file okay so we'll use
parenthesis here so that we can do some
multip Le
Imports and we'll be using the import
path that matches the module name of our
my strings package so in my case it was
github.com Wags Lane slm
strings and then here we just copy this
code from the instructions and in fact
let me space this out so we can see it
just a little bit better Okay cool so
we've imported the myrings package and
then down in our code we we can use my
strings which is the name of the
package.
reverse to call that exported function
okay now because we've added a
dependency we need to update our go dood
file so edit the hell Go's go.mod file
to contain the
following this is the important part
down
here so we need to
add replace example.com username my
strings with do dotm strings and require
example.com
username myrings version z now I do need
to update this right because I didn't
use example.com I used github.com Wags
Lane now what's going on here right Go's
dependency management is very heavily
based on git and remote URLs so normally
what you would do is take your my
streams my strings package and push it
up to GitHub and then import it from
there and and sort of all of the go
packages in the world that require the
my strings package will point up to that
remote location that remote server what
we are doing here with this replace and
require well specifically the replace is
we're kind of doing a little hack to get
things to work locally without having to
publish to get so we're basically saying
I want you to take this string this
import path right github.com waglan
myrings and don't go look for it out on
the internet instead just resolve it to
this path dot do my strings right and
now because well I should probably get
back into the hello go
directory right do do/
myrings this is the directory containing
the my strings package
right so we're basically just saying
replace this with my local uh file
system we're telling go how to find this
package on our machine so now that we've
made those changes uh we can build and
run this program again so go
build slh
heloo build and run and it looks like
it's working correctly this this
function is supposed to reverse the
string and we get this nasty little
and the question is just asking us what
was printed and that's what we got on
the same step this next question asks
how does the go tool chain know where to
find the imported code npm hosts the
files publicly it downloads it from
Google's servers we use the replace
keyword in the Goot dood to point it to
the relative location of myrings or it
was fetched from GitHub now again I want
to point out that most of the time
you'll be fetching code from GitHub or
gitlab or some remote Source when you're
using thirdparty dependencies in our
case we used this replace keyword uh to
point the go tool chain to the location
of the myrings package on our machine so
we already briefly talked about this but
I just want to reiterate that this
little replace hack is useful for kind
of testing and doing things on our local
machine but it's not suitable for
production generally what you would do
is push up that my strings get repos
repository to GitHub and then import
from there so you will typically not see
replace aliases in production go dood
files very often you'll just see um
packages sort of required vanilla from
their remote location so now we're going
to practice using a remote thirdparty
module says create a new directory in
the same uh Parent Directory as hello
and myrings called Date test so let's go
back up one level and create a new
directory make dur date test and then
I'm going to go ahead and again reopen
vs
code into that
directory okay create a main.go with
this
code we can do
that next initialize a module so we do
to go mod and it and again I'll just use
good
hub.com Wags Lane SL date
test creating new go.mod to add module
requirements and Subs do go mod tidy
okay cool um I'm going to ignore that
for just a second and instead I'm going
to follow the instructions it says
download and install the remote go tiny
date package using go
get Okay so we've used go install in the
past right go install installs an
executable on our machine to be used
anywhere um goget is how we go download
and install third party dependencies so
we're going and grabbing the github.com
waglan slgo tiny time
module and it gets added
automatically to our go.mod you'll see
that it just kind of grabs the latest
version cool um print the contents of
your go onod uh I don't need to run that
because we've got it open here in vs
code um and then compile and run our
program again so let's do that go
build and run and it's going to compile
to a file called Date test right because
that's the name of our
directory cool and as you can see it's
printing out this date here so what did
we do right we used goget to download
code from this remote location which
also happens to be the import path right
because that's how go packages work
typically so let me go grab that and
just show you this
is this is a package that's hosted on
GitHub so the go tool chain actually
went to GitHub and downloaded this code
right added it to the go dood as a
dependency and then created this new
file called go. sum that kind of
contains any uh transient dependencies
or dependencies used by the go tiny time
package that we just imported so kind of
keeps track of everything that we're
using and the question for this one is
how did the go tool chain know where to
download the Go tiny time package oh man
this uh this question is not optimized
for this ridiculously zoomed in view it
says the module import path is used for
remote lookups EG github.com Wags Lane
go timing time um or the go tool chain
has free open source go modules location
memorized uh that is not true it just it
just does a remote lookup right it goes
and fetches that from the remote git URL
and then just to make sure that we did
the step correctly uh it's asking us
what was printed after running the new
date test program can see it right there
it looks like it's this
2020-4 d03 right it's it's this
hardcoded date in RFC 3339 format
so hopefully that last section gave you
a taste of local development at least
showing you how to build and compile
some code on your own machine we'll
obviously be doing a lot more local
development at the end of this video
with the project uh but for now for the
rest of this course we'll be back
working in the boot Dev web interface
cool so we're on to talking about clean
packages and this is kind of just a an
article that I'm not going to bother
reading out loud on screen but I'll talk
about each individual section as the
question questions are asked so the
question is should you export code from
the main package and that's talked about
um down here it's basically simple don't
export code from the main package and if
you think about it it makes sense
there's no reason to capitalize function
names in the main package because no
other packages can import from a main
package a main package is built into its
own Standalone executable it's not a
library so capitalizing functions in the
main package is just kind kind of
confusing because it kind of signals to
developers that you're writing code
that's meant to be imported and it's
not this next question says when should
you not export a function variable or
type now this is an interesting question
I think a lot of newer developers think
well maybe I should just export more
stuff because maybe the users of my
package will find it useful um you
actually want to think in the reverse
you want to export as few things as
possible when you're building Library
packages because anytime you export a
function you now need to support that
function right you can't really take it
away later everyone's code will break
right so you should really think about
what you're exposing to your users as
like the surface area of a shape and you
want to keep that surface area as small
as possible to keep the maintenance
burden down and to make your package
easy to use right the few things that
your users need to know about in order
to effectively use your package the
easier it'll be to use so when should
you not export a function variable or
type um when the end user doesn't need
to know about it hide everything that
they don't need to know about the next
question is should you often change a
packages exported API so API stands for
application programming interface and
basically anytime you export something
from a library package uh you're adding
that thing to the package's API right
it's the interface that other developers
will use to access your package so
should you often change a package's
exported API the answer is that no you
should try to keep changes to your
internal functionality right imagine if
the go programming language uh let's say
the standard Library changed fmt do
print line to fm. print right and just
removed fm. print line that would break
and an immense amount of code right so
you don't want to go about uh publishing
packages that you intend to kind of
update the API for uh very rapidly
stable apis are good apis so should you
often change a package as exported API
no try to keep the changes to internal
functionality yes move fast and break
things or if the package is main then
yes uh the answer is just no try to keep
changes to internal functionality again
if the package is main then it's
irrelevant because you're not building
uh a
library I want to talk about the
difference between concurrent or
parallel programming and synchronous or
sequential programming now it's worth
pointing out at this point that
concurrency and parallelism are
different and we'll talk about their
differences in a future course for for
now we're going to kind of treat them as
the same idea and I'll just kind of
refer to them as concurrency uh
similarly sequential Pro programming and
synchronous they're slightly different
terms but um I'm going to kind of bucket
them for the purpose of this discussion
we'll be really comparing uh concurrent
code to synchronous uh or sequential
code so let's start with the easy one
let's talk about synchronous or
sequential programming say we have some
code x colal 5
x++ and then maybe fmt do print.
print line
X the nice thing about this code and
what makes it synchronous or sequential
is that the code executes in order from
top to bottom right first we create a
variable named X set equal to 5 then we
increment it by one and then we print X
right everything's happening in order
from top to bottom one thing at a time
now this is usually what you want it's
simple it's easy to reason about it's
easy to write code this way the problem
is sometimes it's not the most efficient
way to write performance code or code
that can run as fast as possible on a
given set of Hardware so in order to
understand how we can maybe optimize
this we need to look into how your
computer's
CPU or Central Processing Unit
works so your CPU has a clock speed um
which basically like from a very high
level means how many instructions how
many
instructions it can do per let's say set
those aren't necessarily the units but
it is basically how many computations it
can do um per amount of time we just say
it seconds to keep things simple okay so
when we're analyzing this code over here
on the right basically what needs to
happen is first we set x equal to five
and that takes up one instruction right
after that we get to increment X by six
that'll take up another instruction and
then we get to print X so the speed at
which we can execute this little program
here is dependent on how fast the cpu's
clock speed is so if we want our program
to go faster we can basically do one of
two things either we can reduce the
number of instructions required uh for
our program to execute or we can get a
faster CPU one that can do more
instructions per second the problem is
getting a faster CPU sometimes is really
really expensive and so instead of
getting a faster CPU what we've kind
have done over the years is instead add
more
CPUs and in this case um we've kind of
broken the CPU up actually into what we
call different cores so you might
actually have a quad core processor Each
of which let's say does I don't know
5,000 instructions per Nan I'm totally
making these units up um but you get the
idea any one of these CPU cores can only
do 5,000 but the interest thing is they
can all do 5,000 per nond at the same
time so if we distribute our program
across all of the cores in theory we
should able to go uh we should be able
to go about four times as fast again if
we use all four cores instead of just
one the problem is that most code we
write can't take advantage of all four
cores for example let's look at this
code over here we set x equal to 5 and
then we increment it and then we print
it it has to happen in 1 2
3 right in order 1 2 3 we can't do all
of these instructions at the same time
if we did then x++ would have nothing to
increment because we never would have
set um an X variable equal to five in
the first place and print line X would
have nothing to print because X hasn't
been set yet so writing concurrent or
parallel code can drastically speed up
the performance of our programs because
we're able to distribute um kind of all
the instructions that we need to compute
across multiple cores the problem is we
do actually need to write our code in a
different way we need to expect that
some of the instructions are going to
happen at the same time and that's
what's going to speed up our program so
let's say we had a little bit more code
in our program here maybe we have X or
excuse me y colal
6 y
-- FM
t.
line
Y okay this code here in this block kind
of dependent on each other right we have
to do it in one two three order um and
the same goes for this block here but we
don't have to necessarily do this block
of code where we calculate and print X
before or after we calculate and print y
they're they're kind of separate in that
way so what we can actually do is take
this block of code code and execute it
in one core and at the same time execute
this other block of code in another core
doing them at the same time so let's
sort of visualize the runtime of this
program here if we run this code
synchronously or sequentially right not
taking advantage of two CPU cores but
instead of doing it all in one core then
our running time might look something
like this I don't know let's just say 6
nond but instead if we run it across two
cores in
parallel each core executes at the same
time and they'll each take approximately
let's say three
nond so we're done in half the time
again because we took advantage of the
hardware that was available to us so to
wrap up writing concurrent code in many
cases can drastically reduce of how long
it takes to run our programs and it
depends just a little bit on kind of
fundamentally what we're trying to do so
in this case we literally could take a
program and chop its runtime in half by
utilizing a little bit more Hardware so
this chapter is going to be all about
concurrency in the go programm language
and the great thing about go is how easy
it makes it to write concurrent code so
the question is how do we write
concurrent code and go what's the syntax
well there's actually a really awesome
builtin keyword word to the go program
language the go keyword and it spawns a
new go routine when you use it now a go
routine is kind of unique to the go
programming language but at a high level
you can just think of it as a separate
thread of execution we're essentially
telling the go programming language hey
all this stuff in this function right
that I'm calling with the go keyword can
happen in parallel right we can go
execute that on another core of the CPU
if we need to so whenever we use the go
keyword execution kind of immediately
jumps to the next step after the
function call right so this do something
function will kind of go be executed in
parallel right and then execution
continues in the current function um
kind of just on the next line and then
as a last note before we jump into some
code to see how this really works when
we use the go keyword we're not able to
capture any return values from this
function call which makes sense right
because we're kind of moving on we can't
wait for the function to finish and
return some stuff that's the whole point
we want the function to go kind of do
its thing on another thread so let's try
this out in an assignment says emo we
send a lot of network requests right
requests over the Internet each email we
send must go out over the internet to
serve our millions of customers we need
to we need a single go program to be
capable of sending thousands of emails
at once right so we need to be able to
do lots of things things at once this is
what go routines allow us to do edit the
send email function to execute its
Anonymous function concurrently so that
the received message prints after the
sent message so let's go ahead and run
the code in its current
state and we get email received hello
there Stacy email sent hello there Stacy
right so it looks out of order now
here's our send email function it takes
a message um as a string parameter and
the interesting thing is it calls this
Anonymous function here right so it's
defining this function and then
immediately calling it um where it's
going to wait for 250 milliseconds and
then print email received and then this
function will exit and it will print
email sent right and then print the
message so again that's why this is out
of order right we're we're executing
this function first and then we're
moving on to the next line so the way we
fix this is by using the go
keyword to execute this bit of code at
the same time is this bit of code right
the go keyword will immediately move
down to line 13 on the main thread or
the main go routine and spawn a new go
routine to kind of do this in the
background and because of this waiting
time the email received message should
happen afterwards so let's go ahead and
run this again
sent received sent received looks good
to me so you may have been thinking to
yourself well how useful is it to call a
function in a go routine if I can't even
get the return values the answer in go
is that we use channels typically to
kind of resynchronize our code so
typically we'll use go routines or the
go keyword plus some function to go do a
bunch of computation all at the same
time and then we can kind of
resynchronize either in the main go
routine or in some other go routine by
passing data back and forth between go
routines using channels a channel is
really just a thread safe or go routine
safe cue we can put stuff in one end and
read it out the other end in the same
order so we'll typically have one or
more go routines kind of sending data
into the channel maybe they finished a
calculation they're returning their
results over the channel and then we'll
have one or more go routines on the
other end reading that data off of the
channel and processing it in some way
maybe sending a report maybe printing it
to the console so this is the Syntax for
making a channel and channels are typed
so we can say this is a channel of
integers and then we can send data into
the channel using this Arrow operator
it's it's really pretty intuitive we're
saying 69 is being sent into the channel
and then it's actually the same operator
to read a a value out of a channel we
just move it over to the other side
again super intuitive we're reading a
value out of the channel and saving it
into the variable V in this example now
it's important to understand that both
of these operations sending and
receiving on a channel are blocking
operations so if I'm trying to send a
value into a channel and there's no
other go routine on the other side that
will be able to read it out then my code
will actually stop and wait on this go
routine until there is a reader ready
and the same goes for reading when a go
routine gets to this section of code if
there's nothing being sent into the
channel on another go routine then this
code will just sit and wait until
something is sent so let's move on to
the assignment says run the program
you'll see that it Deadlocks and never
exits so a deadlock is basically when
all the go routines in a program are
blocking and there's nothing for them to
do it means there's a bug in the code so
let's run the code just to see what that
feels like although I think it's just
going to feel like it's running the code
for a long time so I'm going to go and
cancel
that the filter old emails function is
trying to send on a channel and there's
no other go routin running that can
accept the value from the channel okay
so let's take a look filter old emails
takes some emails as an input creates an
is old Channel and that looks like it
Loops over the emails and if they are
before a certain date it's going to pass
the Boolean true into the channel
otherwise it's going to pass in false
and then once that Loop exits it's going
to read out of the channel here and
print to the console you know whether or
not the emails uh were old Okay
cool so this makes sense this makes
sense why this is blocking right this
first send either here or here which
everyone happens first is going to block
because the
reader like that code hasn't happened
yet this is the same this is the same go
routine right this is not happening at
the same time as this is happening so
we'll use a go routine to fix it fix the
deadlock by spawning an anonymous go
routine to write to the is old Channel
instead of using the same go routine
that's reading from it okay cool so we
need to run this Loop essentially in a
new go routine so we'll do go Funk and
we'll just use an anonymous function
here because we don't really care about
naming
it and we'll just run that entire block
of code
in a new Anonymous function so now this
and this will happen at the same time
let's go ahead and run
that cool and this looks like what I
would expect I'll go ahead and submit
that so there's this concept of a token
when we're talking about concurrent
programs a a token is basically a unary
value right so not not binary there's
not two possible values true and false
unary there's just one possible value
and when there's just one possible value
we really don't care much about that
value it's not interesting in any way
right so it's not that we care what is
passed when we're working with tokens
it's that we care when and if something
is passed through a Channel at all so
for example this code here is reading a
value out of this Channel and it's just
discarding whatever it reads out it
doesn't even care what it is it's just
waiting to see uh kind of when something
thing comes through the channel so let's
move on to the assignment um and kind of
put that into practice it says our maleo
server isn't able to boot up until it
receives the signal that all of its
databases are online and it learns about
them being online by waiting for tokens
which are just empty structs um on a
channel complete the wait for DBS
function it should block until it
receives numb DBS so this integer here
um tokens on the DB Chan channel right
so this is a channel of empty structs
and again empty structs are tokens
they're unary values um they're not
interesting they don't have any Fields
each time it reads a token the get
databases Channel go routine will print
a message to the console for you okay so
I'm guessing that's down here yeah it
looks like it's going to watch um and
and print here okay so let's get started
and write some
code it should block until it receives
numb DB's tokens so it's a variable
amount of tokens so I guess the easiest
way to do this would be for with a loop
so 4 I col equals
0 I is less than num
DBS I
++ and then in here we're just going to
wait for a token to come across the DB
channel right so it'll block once and
then it'll move on to the next iteration
of the loop block again move on until
we've waited for you know numb DB's
tokens um and then block until it
receives the tokens each time it reads a
token that's it looks like we don't have
to return anything so let's run
that database one is online two is
online this looks good to me so now
let's talk about buffered channels so
the channels we've been using up until
this point haven't really stored
anything in them right we needed a
sender and a receiver at the same time
as soon as the sender sends the receiver
receives there's nothing kind of stored
in the channel at any given time but
that is exactly what a buffered channel
is so we have a buffer of some length
let's say five different items and
senders can actually send into the
channel even when there's no receivers
until the buffer fills up so a sender
could send five things into the channel
and then the buffer is full the channel
is full right and when a receiver
finally goes to read and pop items off
of the other end it will read and pop
them off in order one by one until the
channel is empty so this this is the
syntax we'd use if we want to make a
Channel of integers with a buffer size
of 100 we just use that optional second
parameter um to the built-in make
function so let's move on to the
assignment it says we want to be able to
send emails in batches a writing go
routine we'll take an entire batch of
email messages to a buffer channel uh
we'll write an entire batch of email
messages to a buffer Channel and later
once the channel is full a reading go
routine will go read all of those
messages so we can actually go find
where that's happening here in the test
Suite okay here so send emails um oh
nope this is where they're getting read
I'm sorry ah I'm silly the function that
we'll be working on is what's writing
into the channel so emails to send is
the channel um we're supposed to write
an entire batch of emails into the
channel and then theoretically sometime
in the
future those emails will be read out of
the channel and actually sent off
Okay cool so what happens if I run the
code in its current
state I'm kind of imagining that it's
going to block
forever yeah that's never going to exit
so at emails toq is getting called here
and send emails is getting called here
and notice that they're getting called
on the same they're both running in the
same go routine Right add emails to Q is
there's no go keyword anywhere that I
see so that's why we are blocking here
there's no reader on the other end right
but because we want to do this batching
we actually should be able to do it all
on the same thread we just need a big
enough batch size so we need a
buffer if we buffer this emails to send
Channel with the length of the emails
slice then we should have enough room to
push all of the emails into the buffer
Channel let's go ahead and try that
cool that looks like it is working
channels in go can actually be closed
and there's really only one reason you'd
want to close a go Channel and it's to
indicate that you're done with it a
channel should always be closed from the
sending side so the sending go routine
the one that's pushing values into the
channel will be the one to close the
channel and that will indicate to any
readers of the channel That this channel
is closed there's nothing else to read
from it and the actual syntax is really
simple it's just the built-in close
function where we pass in the channel
itself now on the reading side we can
actually check if a channel is closed
using this kind of optional second
variable um that comes back when reading
a channel this okay variable it's a
Boolean uh if the Boolean is true the
channel is open if it is false the
channel is closed if the channel happens
to be buffered then okay will remain
true until the channel is emptied out so
okay will only be false once the buffer
channel is empty and closed the last
thing I'll mention before we jump into
the assignment is that you never want to
send a value across a closed Channel if
you do that go routine will panic and
that's why it's really important that
you only ever close a channel from the
sending side because that go routine is
going to be the one that knows when it's
done sending values okay on to the
assignment it says Amo we're all about
tring what our systems are up to with
great logging and Telemetry the send
reports function sends out a batch of
reports to our clients and reports back
how many were sent across a channel it
closes the channel when it's done okay
so the send reports function here sends
the reports or sends the number of
reports across the channel and closes
the channel
great complete the count reports
function it should use an infinite for
Loop to read from the channel if the
channel is closed break out of the loop
otherwise keep a running total of the
number of reports sent and return the
total number of responses Okay cool so
first things first we need to keep track
of a total so let's start it at
zero and we know that at the end of the
function we'll be
returning the
total
right then it says use an infinite for
Loop to read from the channel okay so
four um and then we need to read from
from the channel so we'll do um nums
Cent and read from the nums Cent
Channel okay if the channels closed
break out of the loop ah right so we
need to use that syntax to check if the
channel is closed so if not okay if the
channel is closed then we'll
return
total or I suppose we're returning down
here so we could just break let's just
do that instead break
otherwise uh we do total plus equals
nums Cent and it's important to
understand that as soon as okay is false
num scent will just be a zero value so
it's not a valid value that was sent
across the channel so it's safe to just
just break like that okay cool this is
looking correct to me let's go ahead and
run
that let's see batch of 15 38 and 61
Cent do those add up to 114 uh looks
like they might uh like my terrible in
in my head arithmetic says that that's
perfect so I'm going to go ahead and
submit it the range keyword works with
channels as well as slices and Maps so
we can actually range over the channel
and it will block until a value is ready
it will read it into in this case the
variable item and then execute the body
and it will do that over and over and
over for each new value coming across
the channel um and we'll only exit the
loop once the channel is closed so onto
the assignment it says it's that time
again maleo is hiring and we've been
assigned to do the interviews for some
reason the Fibonacci sequence is melo's
interview problem of choice we've been
tasked with building a small toy program
that we can use in the interview okay so
complete the concurrent Fibonacci
function if you're not familiar with the
Fibonacci sequence um I don't want to go
like too crazy far into it but it's
basically this sequence of numbers here
where each number is the sum of the two
preceding numbers so 1 and 2 equal 3 2
and three equal 5 in fact you probably
can't even see that let me Zoom this in
a little bit so 1 and one or 1 and two
equal 3 2 and 3 equal 5 3 and 5 equal 8
right so it's just this kind of set
sequence of
numbers okay complete the concurrent FIB
function it should create a new channel
of ins so let's go ahead and do that so
Channel
ins make channel of ins great call
Fibonacci in a go routine passing to it
the channel and the number of Fibonacci
numbers to generate okay so we have a
Fibonacci function here and it takes the
numbers and the channel itself so let's
start a go routine in an anonymous
function go
Funk call the
function um and in here we we call
Fibonacci let me just copy and paste
that because I can't spell
it uh it takes n
and this channel of in as input
okay then use a Range Loop to read from
the channel and print out the numbers
one by one each on a new line okay so
the Fibonacci function is going to pass
its results back over this channel so
it's it's taking the channel and it's
going to act as the sender on the
channel so we down here below um kind of
where we're spawning that go routine
need to act as the reader so for V and
range Channel
events print out the numbers one by
one print line
V and then when the Fibonacci function
is done it will close the channel so
we'll exit from the loop cool let's go
ahead and run
that does this look accurate 10 numbers
0er and one 0 + 1 is 1 1 + 2 is 3 2 + 3
is 5 looking good to me on to select
statements so a select statement is
similar to a switch statement if you're
familiar with switch statements from
other languages or from go where it's
kind of this if else uh chain and we're
really looking to kind of match a
specific case but what's interesting
about select statements is they're
unique to channels and they basically
let us listen on two different channels
and kind of execute one block of code um
or or or rather execute a block of code
for the channel that sends a value the
soonest so we can kind of simultaneously
listen on both channels and if this
channel sends a value first then we'll
do you know one thing otherwise when
this one sends something we'll do
another thing so one go r can kind of
process events from multiple channels at
the same time using a select statement
so this is the syntax here we create a
select block and then multiple cases one
for each channel that we are interested
in kind of listening for values on or
reading uh values off of so if this
channel ins has a value ready then this
case will be executed otherwise if this
one uh if Channel strings has a value
ready then this case will be executed if
both have a value ready at the same time
then one will be chosen randomly so you
don't want to kind of be dependent on
any weird ordering um basically as stuff
comes across it's going to get processed
so on to the assignment it says complete
the log messages function using an
infinite for Loop and a select statement
to log the emails and SMS messages as
they come across the two channels okay
so channel of emails channel of SMS
messages add a condition to return from
the function when one of the two
channels closes whichever is first um
use the log SMS and log email function
functions to log the messages Okay cool
so uh infinite for Loop start there and
inside we'll need to
select can use this as our syntax guide
over here U we got case uh
okay value from channel of
emails if that case fires then we say if
not okay so if the channel is closed we
just return and this function returns
nothing so we just naked return um
otherwise we need to log email I think
that's yeah log email there it is the
email cool other case uh sms okay from
the channel of
SMS if not
okay
return
otherwise log the
right so this for Loop will just go
forever until one of these two channels
closes in which case it'll return right
and it's important that we return before
we do the logging because when a channel
closes the okay variable um is false and
a zero value comes across the channel um
that wasn't sent into the channel if
that makes sense so like the closure
happens after the last value comes out
let's go ahead and run that
looking good to me we'll submit it the
select statement also has a default case
you don't always need to use the default
case and basically the default case only
fires if you're interested in
nonblocking so for example this select
block here will fire the case uh that
kind of pulls this V value out of this
channel if there is a value ready to be
pulled at the time that we enter the
select block if there's not a value
ready to be read out of the channel um
then the default case is fired
immediately so it kind of turns the
select block into a nonblocking block of
code and then there's a few things we're
going to need to know for this
assignment the first are these uh
several standard Library functions so
time. tick returns a channel that sends
a value on a given interval so we could
say uh pass in a second to time. tick
and we would get back a channel um that
receives a value once per second pretty
cool for like rate limiting doing
something on a set interval um time.
after sends a single value after the
specified time has passed so if we
created a new Channel with time. after
and passed in one second then one value
would come across after a second had
passed and then the channel would be
closed um time dos sleep just blocks for
the specified amount of time in fact I
think we've kind of seen it around um so
far in the course and then we have read
only and write only channels and these
are pretty cool from like a type safety
standpoint uh basically we can take a
channel and we can pass it into a
function but specify that within the
function we are just going to read from
it so then within the function uh the
compiler will not allow us to write to
it so this is a great way to kind of
keep uh go routine safe in terms of
which functions are readers of channels
and which fun fun are writers of
channels um and we can do the same thing
with writers by using this syntax here
so readers read out of the channel
writers right into the channel the
assignment says like all good backed
Engineers we frequently save backup
snapshots of the maleo database complete
the save backups function okay it should
read values from the snapshot ticker and
save after channels simultaneously okay
so the ticker and the save after these
are channels that are created down
here us those standard Library functions
that we just talked
about okay so we need to use a select
statement it looks like read values
continuously so I'm going to use a for
Loop and a select
statement if a value is received from
snapshot ticker call take snapshot okay
so case Val okay we want to read out of
a snapshot ticker
um do we actually do we care about the
channel being closed let's see saved
after no actually we don't so we just
need the value from snapshot
ticker oh and we don't even care about
what the value is so we can just
completely ignore the value um call take
snapshot
okay if the values saved after so
case save
after call Save
snapshot and return from the function
because we're done okay so again we
don't need to return anything from this
function doesn't have any return values
if neither channel has a value ready
call wait for data okay so
default wait for
data and then sleep for 500 milliseconds
so time do sleep
time.
millisecond time
500
okay this is looking good so we've got
this infinite for Loop we'll we're
looking for values um that are coming
across these channels anytime a snapshot
should be taken we take it anytime um
this save after Channel passes us a
value we will save the snapshot and exit
the function otherwise we'll wait for
data whatever that does and sleep for
500 milliseconds in fact I'm curious
what does wait for day to do okay so all
these do um is kind of Print Different
messages so let's go ahead and run
that okay cool so nothing to do waiting
nothing to do waiting right so every 500
milliseconds we're kind of logging out
that we are waiting we're waiting for
things to to do um taking back backups
taking backups and um finally saving the
backups at the end cool let's submit
this looks correct to me Dave Cheney is
a fantastic author and contributor to
the go programming language and the go
ecosystem um and he has this awesome
article that I would definitely
recommend checking out it's called
Channel axioms um I've kind of
summarized it here and we're going to
talk about a few key points so the first
is that Ascend to a nil Channel blocks
forever so the zero value for a channel
is nil if you don't use the make
function to initialize a channel then
the Channel's value is just nil and if
you try to send on a nil Channel your
code's just going to block forever
similarly a receive from a nil channel
will just block forever now this one's a
lot more dangerous trying to send on a
closed Channel panics so you need to be
really careful and make sure you're only
closing channels from the go routine or
the function that's writing to the
channel so that you can be sure you'll
never try to write to that closed
Channel again and then then a receive
from a closed Channel Returns the zero
value immediately so if you're trying to
pull data out of a channel and it's
already closed you'll just get the zero
value and we've already seen how you can
also kind of optionally check for that
okay value uh which will be returned as
false so what happens when you read from
a nil's channel uh the receiver will
block forever and what happens when you
send to a closed channel uh you'll get a
panic and you never really want to panic
and go that's almost always indicative
of a bug so channels are one tool that
we have in go to sort of synchronize
State across different go routines right
we can send data from one go routine to
another safely mutexes are another
builtin primitive well rather they're
not built into the language but they're
built into the standard library and they
allow us to also sort of communicate or
share data between two go routines
mutexes work by locking access to
protected resources so that only one go
routine can access that resource at a
time so the sync. mutex type is exposed
by the standard library and basically
the way it works is you create a new
mutex and then you share it across many
different go routines and then you wrap
whatever code that that is dangerous or
whatever resource that is dangerous um
that you never want two go routines at
the same time kind of getting access to
you wrap them in a call to lock and
unlock so for example in this function
we have this protected function and it
calls mx. lock at the top of the
function and then it defers an unlock
and what this means is when one go
routine calls this protected function um
if it's the first call to this protected
function then this mx. loock will lock
the mutex for this go routine and it
will be able to move on and kind of
complete the rest of the function and
then call uh m tex. unlock every other
go routine that calls this function
right on this shared mutex will actually
block at the mx. loock function call
because the mutex is locked by another
go routine so mutex stands for Mutual
exclusion because it excludes every go
routine except one except for the one
that holds the lock and once that first
one actually unlocks the mutex then one
other go routine is able to pick up the
lock um execute its protected code
and move on from there so it's a way to
take some dangerous resource and sort of
share it across many different go
routines uh safely there are a couple of
different reasons why you would want to
protect a resource and only allow a
single go routine to kind of work on
that resource at a time but one super
common one is Maps so maps are not
thread safe in go if two different go
routines or trying to read from and
write to the same map uh go will
actually panic
uh because that has a very high
likelihood of causing a race condition
and if you're wondering what a race
condition is a race condition is kind of
what it sounds like it's when two
different go routines effectively racing
uh to get access to a specific resource
so imagine a case where we have a count
variable and it's set equal to maybe
five and you have one go routine that
reads that count variable and saves it
into say another variable my count right
and then maybe doubles it and then saves
the doubled value back into count well
what if we have another uh thread of
execution doing the same thing if they
both read count let's say they both read
five
double five so now they both store 25
and they both go to write 25 back into
the variable all you did was double five
once where you were probably expecting
to have two different threads doubling
the variable so you probably were
expecting for it to go 5 to 25 and then
double again 25 s 625 I think um the
point is depending on exactly how those
operations are threaded into the CPU
you'll actually get different Behavior
so race conditions are awful to debug
and mutexes allow us to lock access to
those protected variables so we could
wrap that count variable in a mutex to
avoid it being accessed at the same time
by different go routines so this should
make a lot more sense once we write some
code the assignment says we send emails
across many different go routines at
maleo to keep track of how many we've
sent to a given email address we use an
in-memory map Okay so we've got this
counts map stored within a safe counter
struct the map is of string to integer
and then we've got a pointer to a mutex
uh that we're sharing across all these
different go routines um our safe
counter struct is unsafe update the
increment and the value methods so they
utilize the safe counters mutex to
ensure that the map is not accessed by
multiple go routines at the same time
okay so we've got the safe counter and
it looks like it's going to be kind of
copied across different go routines so
we create one instance of the safe
counter um it's getting passed into this
test function and then we've got this go
routine um it looks like we're spawning
go routines in a for Loop and then uh
and then using that safe counter right
so increment is being called many
different times across many different uh
threads of execution and that is not
safe cool um so let's see what happens
if I run the code right
now Jill has three emails John has
four and if I look down into the tests
like that is wildly inaccurate right so
it it kind of looks like what's
happening is is like the example I
described where maybe you're multiplying
numbers together at the same time so you
kind of end up with a smaller number cuz
everyone's doing it at the same time
then saving the variable back it kind of
looks like that's what's happening we're
getting these tiny results cuz all of
these different go routines are
incrementing uh the number at the same
time okay so we need to lock this
down
sc.
m.m. loock so lock
the the mutex and then defer the unlock
it's important to understand that we
could do this and it would be the
same um right we lock we do the
dangerous thing and then we unlock um
but this is the
preferred the preferred way kind of the
idiomatic go way to do it um it just
it's just a little safer right if we add
multiple Returns the defer will never
not execute so um that's how we're going
to do it and then we also need to lock
um on the Val function because reading
um and writing at the same time is also
dangerous cool let's run that see if we
get some bigger numbers in our
counts notice it's taking a lot longer
to run and and that's because we're
locking and unlocking so every thread
has to kind of go one after the other so
it slows it down um but it's a lot safer
so we get these accurate counts let's go
ahead and submit
that so as we discussed am mutex is
called a mutex because it's short for
Mutual exclusion and the conventional
name for a mutex is um mutex or
sometimes it's abbreviated to MX
personally I use MX a lot so to answer
the question what does it refer to it's
going to be mutual exclusion the next
question is how many threads or go
routines can lock a mutex at once or at
the same time the answer is one right if
any more could lock it at the same time
time it wouldn't be very useful the next
question is why would you use a mutex to
safely access a data structure
concurrently to stop other packages from
using my code to protect data from
network access or to encapsulate private
data members of a struct it's going to
be to safely Access Data
concurrently time to talk about another
type of mutex so we've already covered
pretty much all of the functionality you
need to know about the sync. mutex type
but there's another type there is a
sync. RW mutex or sync. readr mutex so
the sync. readr mutex has a little more
functionality um it has the same lock
and unlock methods and works exactly the
same way as a normal mutex but
additionally it has two more methods
rlock and R unlock so the reason you
would use a read write mutex is because
it allows you to have multiple readers
at the same time for example it's
actually safe to read for from a map on
multiple go routines at the same time as
long as you're not reading and writing
at the same time or writing and writing
at the same time writing is the
dangerous operation so the way this
works is if the mutex is locked like
with the do loock method then no other
go routines can lock or arlock the mutex
however if the mutex is just are locked
then other mutexes can still our locket
so you can get multiple readers but you
can't get readers and writers or writers
and writers it just allows many threads
to concurrently read so what this does
is if you have a program with a shared
resource where most of the threads are
interested with reading the shared
resource and you only have a few writers
you can make your code a lot faster by
using a read write mutex and allowing
multiple readers at the same time your
code will move faster and have fewer
locks slowing down
um and synchronizing your application so
on to the assignment it says let's
update our same code from the last
assignment but this time we can speed it
up by allowing readers to be read from
the map concurrently update The Vow
method to only lock the mutex for
reading notice that if you run the code
with a right lock it will block forever
Okay cool so if I run try to run it
right now um it's going to have issues
because I believe it is are locked um by
the test Suite out of the gate so that's
a problem um we can fix
it by changing those to rlock and R
unlock perfect looks good to me I'm
going to go ahead and submit that so a
quick review on read write mutexes how
many writers can access an RW mutex at
once or at the same time uh the answer
is one and how many readers can access
an RW mutex at once it's going to be
infinite there is no limit the next
question is can readers and writers use
RW mutexes at the same time the answer
is still no right no one can use the
mutex if it's being written to generics
are an amazing new feature in the go
programming language at least they are
new as of the time of this recording
we're on version 1.20 now and generics
were just released in 1.18 and they were
one of the most widely requested
features by go developers so let's talk
about what generics are and why they're
so useful let's take a look at this code
here we have a function called split in
slice so it takes a slice of ins and it
splits it into two smaller slices of
integers it effectively just splits the
slice in half and returns two new slices
the really interesting thing about this
function is that if you take a look at
the function body it doesn't actually
care that this is a slice of ins it
could be a slice of strings or a slice
of booleans the logic in the function
body would be the same right calculate
um kind of the midpoint of the slice and
return two new slices um with all the
values up to that midpoint the annoying
thing is that prior to generics in
version 1.18 if we wanted to split a
slice of strings we would actually need
to write an entirely separate function
because in go we have static typing and
we need to tell the compiler what type
of slice it is that we are splitting now
it is true that prior to generics you
could write a function that takes a
slice of the empty interface which is
essentially a slice of anything and then
returns to slices of the empty interface
the problem with that is that you need
to cast the return values back into
whatever they were which is kind of a
dangerous runtime check so generics or
type parameters are really just a way
for us to write that function with the
empty interface but do it in a compile
safe way because if you think about it
the compiler can know that this function
doesn't care about the types going in
but that anytime it's called the types
coming out will be the same as the types
coming in so how does it work well we
effectively have a new type of parameter
that can be passed into a function so
after the function's name we have these
optional square brackets and here we're
defining a new type we're calling it t
and we're saying it is of type any or it
can be anything now because we using T
the type parameter as both the input and
the output we're telling the go compiler
hey whatever I pass into this function
that's what I'm expecting to get back
out right if I pass in a slice of
integers I expect to get out two slices
of integers so now if we take a look at
how the code is actually used right how
the function is called we can use the
split any slice function give it a slice
of integers and we'll get back two
slices of integers everything is type
safe so in a nutshell the reason to use
generics is to dry up our code right dry
is just an acronym for don't repeat
yourself it's a way of writing code uh
that can be reused uh more easily so
instead of having to write two nearly
identical functions just to deal with
two separate types of slices we can now
write a typ safe function once and reuse
it throughout our application so let's
Jump Right In the assignment says at
maleo we often store all of the emails
of a given email campaign in memory as a
slice we store payments for a single
user in the same way so those are two
very different things right emails and
payments complete the get last function
it should be a generic function that
Returns the last element from a slice no
matter the type stored in the slice if
the slice is empty it should return the
zero value of the type okay cool let's
get started okay so we can kind of use
this syntax as a guide so we're going to
need um to specify type it can be
anything like the assignment said and
we're going to take as input a slice of
that type and return one element of that
type okay now it says um we need to
return the last element from a slice so
uh we need to slice the slice
essentially check its length right so we
can say um if the length of s is zero so
if there's nothing in the slice we're
going to have to just
return uh the m empty type of T which
down here it says we can create uh we
can create a new empty variable like
this so we'll just
do
bar zero Val and then we'll just return
it okay cool so if the slice is empty
we'll return the the zero value um
otherwise we'll
return S
at the length of s minus one right okay
so if there's nothing in the slice we'll
return the Z zero value otherwise we'll
return the element in the last index and
the reason we have to do this guard
Clause is because otherwise this would
be an unsafe operation we'd get an index
out of Bounce error uh if we tried to do
this operation on a slice of zero length
so let's go ahead and run
it and it looks
like okay so the test case with a zero
length slice returns an empty struct
that looks correct to me me getting last
email from a slice of length three
there's the last one looks like it
returned it correctly and then here we
have payments right so a completely
different type and the last one is Jane
example.com yep that looks correct to me
Margo Margo cool let's submit it so why
are there generics in the go programming
language well there's kind of three main
points that are important to understand
the first as we already covered is that
generics reduce repetitive code so
whenever we have code that doesn't
really care about the type or maybe it
only about a small superficial part of
the type then we can write more abstract
and more reusable code if we have access
to generics right because it keeps the
code compile time safe and type safe
before in go if we wanted to reuse code
we had to do sort of dangerous runtime
checking right casting things to empty
interfaces and back from empty
interfaces it's also important to
understand that generics are going to be
used much more frequently in libraries
and packages if you're say about backend
developer and you're building
applications um and not libraries or
packages that are intended to be used by
other people um You probably don't need
to use generics all that often which is
why the go programming language was able
to get away with not even supporting
them for so long now if you do work on
libraries or packages or maybe you're
even a contributor to the go standard
library then you probably are going to
use generics quite a bit more they'll
make it a lot easier to write more
abstract code that's useful uh for more
and more use cases the last Point that's
important to understand is just Why did
it take so long to get generics into the
language right they're super useful why
didn't we add them I don't know five
years ago or a decade ago and go was
kind of getting started the answer is
that one of the philosophies behind the
go programming language is just to kind
of keep it simple go doesn't have all
that many features because go developers
and go maintainers don't buy into the
idea that more features necessarily
makes a better programming language by
having fewer features it means there's
less to learn to get up and running with
the language it also means that anytime
you see a piece of go code you'll have
an easier time understanding it and
working with it so even more so than
other languages the go team is very
hesitant to add new stuff so generics
were in the works for a long time and it
was only after many years of thinking
about it and making sure that we really
needed them uh that they actually got
their way into the language so the
question here is is what code would
generics be most likely to help with a
binary tree detecting whether or not a
string contains a given substring or
calculating the area of a circle well
detecting whether or not a string
contains a substring we'll probably just
use string types and calculating the
area of a circle just sounds like floats
to me um but a binary tree is a data
structure that can store any type it's
kind of like you know a slice in that
way um so I'm going to go with binary
tree on that one the next question is
Go's approach to language design is to
support as many useful features as
possible to resist adding new features
until they're extremely important or
proven extremely important or to never
add new features the language doesn't
change um and the answer is to just
resist adding new features unless
they're proven to be very very important
the next question is generics will
probably be used more heavily in blank
main packages which are executable
applications or Library packages and I
definitely argue that they'll be more
heavily used in library packages okay so
we we talked about how generics are
super useful when the type that is being
used in your function doesn't matter
right when it can be anything but
constraints allow us to write generics
that are actually just useful for a
subset of types right so maybe this
function doesn't care too much about the
types that are being used but it does
care a little bit so let's take a look
at an example this concat function is
similar to the other generics that we've
worked with
the big difference is that the type T
the type parameter instead of being able
to be anything we're saying it has to be
a Stringer right and we've specified the
Stringer interface here it's just
anything with a string method on it that
returns a string with the builtin any
constraint which is effectively the
empty interface we can't really do
anything with the values inside the
functions right which is why it works
for something where we're just say
splitting a slice of that type but in
this function the concat function where
we're actually able to use the type a
little bit we're able to call the method
that's implemented on it and that's just
because the only thing that we know
about these values coming into the
function is that they have that string
method so what does the concat function
do well it takes a slice of
stringers it Loops over all of them
grabs the string representation right by
calling that string method the string
representation of each value and then
kind of just mashes them all together
right it concatenates them and Returns
the result so let's take a look at the
assignment we have different kinds of
line items that we can charge our
customers credit cards for line items
can be things like subscriptions one or
onetime payments great complete the
charge for line item function first it
should check if the user has a balance
with enough funds to be able to pay for
the cost of a new item okay so we're
given a new item which is of type T
given a slice of old items and a balance
is just a float um if they don't have
enough funds then we'll return an
insufficient funds error but if they do
have enough funds then we'll add the
line item to the user's history by
appending new item to the slice of old
old items okay this new slice is your
first return value right so we're
returning three values here so we're
returning kind of the new list of old
items after new item has been
appended um and we need to calculate the
user's New Balance by subtracting the
cost of the new item from their their
balance this is my second return value
Okay cool so we Tak in their balance
we're going to subtract the cost of the
new item and then return the New Balance
um and obviously if nothing went wrong
we'll return a nil error okay so we have
the type here as a line item so line
item is this interface and it looks like
we we kind of have these two methods
available to us um within our function
we can get the cost of a line item or we
can get the name of a line item so the
first thing our function is supposed to
do is check if the user has a balance
with enough funds so we need to do
something like uh new new
balance is equal
to
balance
minus the new
item. get cost right so we're going to
check what the New Balance would be
after subtracting the cost of the item
from the user's balance then we can say
if the new balance
it's less than
zero then we'll return this insufficient
funds error right and zero values for
everything else so it'll be uh nil slice
0.0 float
and errors. new insufficient
funds we'll need the errors
package okay cool otherwise that means
this uh this balance is uh zero or
greater right
um and it says add the line item to the
user's history by appending it to the
slice of old items so we could do old
items equals append old items new
item okay this new slice is your first
return value
great otherwi um and then calculate the
the user's New Balance by subtracting
the cost of the new item from their
balance so we already did that um this
is your second return value so I think
we could just return
um old items New
Balance and
nil right let's go ahead and run
that okay charging customer for a yearly
subscription current balance is 1,000
new balance is 750 total number of line
items is now
one for monthly
subscription new balance is less total
number of line items is now three let's
go take a look at those test cases just
to make sure that this makes
sense okay so they had zero now they
have one
subscription here they had two
subscriptions now they have three
subscriptions cool that looks or or
three L items that looks good to me I'm
going to go ahead and submit it when
generics were released a new syntax for
writing interfaces was also released um
partly just because this new syntax is
super useful if you're trying to use
interface faes as constraints for your
generic functions so let's take a look
at an example um a typ list is pretty
much as simple as it sounds we're
basically just creating a new interface
in this case we're calling it ordered
and then we're just listing all of the
types that we're saying Implement that
interface so there's no methods here but
the interesting thing about all of these
types is that they all support these
inequality operators like less than less
than or equal to greater than and so if
we use ordered as a cons straint then
we're free to use these comparison
operators on that type and the question
that goes along with this is why might
you create an interface using a type
list and the answers are you know
exactly which type satisfy your
interface or it's too much trouble to
define the methods required by your
interface um that second one is a bad
reason uh it's you would really only do
this if you know exactly which types
satisfy your interface let's take a look
at parametric constraints which sounds
like a kind of complex term but it's
actually really simple it basically just
means that we can use type parameters in
interface definitions as well so instead
of just creating interfaces which we
then use in our type parameters for
functions we're actually using type
parameters to Define new interfaces so
for example here we have a
store that takes as a type parameter P
which is a product now a product is just
an interface right um it's a product is
anything that can return a price and a
name so a store which is again an
interface is just anything that can sell
a product now the idea really is that
simple if you want to get an idea for
kind of how this whole uh snippet of
code works the example code I'm not
going to talk through every line that
would take a little bit too long but
feel free to kind of browse through this
and get a feel for how um that interface
Works we're going to jump straight into
the assignment so getting on to the
assign assment says the Chief Architect
at maleo has decided she wants us to
implement billing with generics
specifically she wants us to create a
new biller interface a biller is an
interface that can be used to charge a
customer and it can also report its name
okay so let's take a look at what a
customer is so a customer is this
interface it's just anything that has a
get billing email method on it so in
this case a user is a customer and an
org is a customer
okay um and it can also report its name
okay that's simple enough um there are
two kinds of billers user billers and
org billers Okay so we've got user
biller here and org biller
here create the new biller interface it
should have two methods okay so let's go
ahead and do that
type
biller
interface two methods charge and
name the good news is the architect
already wrote the user biller and org
biller type for us that fill this new B
interface right we took a look at
that use the definitions of the types
and their methods to figure out how to
write the biller interface on line 7
Okay cool so let's start with name it
looks simpler so a user biller um has a
name method it takes no parameters and
returns a string uh same with the or
biller so that one's simple it just
returns a
string okay the charge method for user
biller accepts a user returns a
bill but for the org Biller accepts an
org and returns a bill okay so it always
returns a bill that part's easy but what
it accepts is a little bit
different right and if we take a look
at the customer interface where did it
go customer interface here then we see
that we have
um we have this common interface that we
can use to represent both a user and an
orc
okay so that's convenient because we can
use that interface right as a parametric
uh constraint so let's create a new uh
parametric type or you know but a we'll
create a new type
parameter call it C because it's a
customer should be easy enough and then
charge will actually take as input
C right it will take a customer
rather than specifically a user or an
org okay let's try
that it compiled at least let's see what
we got using basic user biller to create
a bill for Joe example.com
$50 basic user biller for Samuel bogs
$50 Pro user biller for Jade
$100 okay that's looking correct to me
it looks like the pro uh Pro and basic
uh have a discrepancy of $50 and $100
and then the org biller is being used
here you can see it's actually much more
expensive so that looks correct to me so
let's talk about naming generic types uh
we can take a look at this old example
or the example from earlier in the
course where we had to split any slice
right and it has t as a type parameter
um of type any and then we use T um kind
of all throughout the function signature
um T is just a variable name it doesn't
we don't have to use T there we could
name we could name this whatever we want
right we could name it my type uh
whatever the heck we want right uh slice
value it doesn't matter um however it
turns out that capital t is actually
fairly common convention um when there
is just a single type parameter for a
function so the question here is the
name of a type parameter blank can and
should be whatever you want can be
anything but T is a common convention or
must be t uh it turns out it can be
anything but but T is a fairly common
conventional name Rob Pike one of the
creators of the go programming language
created an awesome set of Proverbs that
outlines some more of the philosophies
behind the language I'm not going to
read them all to you but I am going to
cover some of the ones that I think are
the most important and if you are
interested I'd highly recommend going
and watching his talk about the Proverbs
uh if if you want to go more in depth on
them so let's start at the top don't
communicate by sharing memory share
memory by communicating so this
basically is telling us we should
probably try to use channels more often
than we use mutexes when we're trying to
share memory between go routines which
leads right into the third one which is
channels orchestrate and mutexes
serialize so when we use channels we get
kind of this elegant flow of data
through our program right one go routine
did something another go routine can
react to it mutexes are all about just
locking access to protected resources if
we can use channels to express logic in
our program it will tend to work a
little bit better another great one is
that the empty interface says nothing
right every type in go fulfills the
empty interface you should really avoid
using the empty interface just because
it doesn't tell you anything interesting
about your types the empty interface is
sometimes used by new go Developers to
kind of get around the type system and
that just leads to unsafe code go format
style is no one's favorite and yet go
format style is everyone's favorite so
we'll get into this in just a bit when
we jump into the project but the go tool
chain comes with its own formatter and
pretty much every production piece of go
code out there uses the same formatter
which is fantastic because it means all
go code looks the same while not
everyone necessarily agrees on the form
matters choice of say tabs versus spaces
it's still everyone's favorite because
it means we all get something consistent
that works reasonably well the next one
is one of my favorites it's a little
copying is better than a little
dependency now if you've ever worked in
the JavaScript ecosystem with node
modules you'll know exactly what this
means node modules is typically a huge
folder and a lot of times when you're
working on a front-end framework your
dependencies are much greater than your
actual application code and in go we
typically just kind of invert that way
of thinking we want our application code
to be the bulk of the code within our
project and we tend to use far fewer
dependencies as almost a philosophy and
this brings us to the one uh mentioned
here in the question which says which is
better clear or clever and at least
according to rag Pike clear is better
than clever so if you can write your
code in a way that is very clear to
anyone else reading it or even just to
yourself that's better than trying to do
a trick that might seem clever but is
actually hard for humans to understand
remember code is written for humans not
for
machines the next question is which is
better copying a little code or
including a small dependency and Rob
Pike would argue that it's typically
better to copy a little bit of code
rather than include a very small
dependency in your project the next one
I want to point out is that errors are
just values right and go error is just a
specific interface and we return it from
functions just like we do any other
value there's not some special way to
handle errors with tries and catches
errors are just values and we deal with
them just like we would any other
value the last one I want to point out
is that documentation is for users this
is actually an interesting one I think
what he's saying here and I I need to go
rewatch The talk to confirm but that
documentation is primarily for people
who aren't maintaining the code in other
words it's best if the code itself is
really easy to understand so it doesn't
need to be heavily documented so that
other maintainers can work on it right
um documentation in the sense that you
know we should be writing external
documents that explain how the code work
or or how to use it should be for the
users of our code right in the sense
that if we're writing a library we
should be writing documentation for the
users of the library um the developers
calling our exported functions right not
the maintainers of the library itself so
documentation should primarily be
written for the users of your code it's
time to build a fully fledged backend
server Ino from scratch on our local
machines the purpose of the server will
be to aggregate data from RSS feeds if
you're not familiar with RSS it's a
protocol that makes Distributing things
like podcast and blog posts really easy
so what our server will allow users to
do is add different RSS feeds to its
database and then it will go
automatically collect all of the posts
from those feeds and download them and
save them in the database so that we can
then view them later before we get
started there are four things you're
going to need the first is a basic
understanding of SQL the language that's
most often used to query relational
databases if you're not familiar with
SQL yet that's okay I've got a full
course on SQL I'll link that down in the
description below go watch that then
come back here if you're not familiar
with SQL number two is you're going to
need a text editor and a command line
I'm using vs code and zsh uh here in the
video so you'll see me using that uh
feel free to go download those if uh you
want to try them out but you can use
whatever text editor you want as long as
it can edit your files and you have
access to a terminal to run commands
number three is the go programming
language itself if you don't have that
you can go download it I will link uh
the download page down in the
description below and I'd also recom
recommend just installing the go plugin
um if you're in VSS code there is an
official go plugin Go download that
it'll make your life easier with syntax
highlighting and formatting and that
sort of thing number four the last thing
you'll need is an HTTP client so an HTTP
client will allow you to make get and
post and put requests into the web
server that we're building we'll need
that for testing I use the Thunder
client it's a vs code extension but you
can use anything you like even curl on
the command line or post and insomnia
there's tons of choices Google HTTP
clients um or if you don't already have
a preference again if you're in vs code
I'd recommend the Thunder client
extension now that you have all of those
tools installed and hopefully working
let's jump into the project the first
thing we're going to need is just a
main.go file we'll create our entry
point um it will be a part of the main
package and it will need a main function
so Funk main takes no arguments return
those returns no parameters and for now
let's just print hello world make sure
we can kind of build and run this
program um we're going to need to
initialize a new module for our project
so I'm going to do go mod in it and I
like to name my modules after their
remote path right where they will exist
um kind of out on the internet so uh in
my case I use github.com my GitHub uh
namespace which is Wags Lane slash the
name of this repository which again is
where I'll be keeping this code on
GitHub so I highly recommend you keep
track of this code in GitHub this entire
project should be checked into git and
uploaded to GitHub or gitlab or whatever
you prefer um so I'm going to name this
repository um RSS aggregator RSS a so
we'll go ahead and create that that will
create this new go
module and now that that's ready we
should be able to just go
build and execute the new uh binary or
the new executable that will be created
from go build in fact I'll just run it
once so you can see I created this new
binary file here in my current directory
and so from now on I'll be running this
command go build and
slash RSS a so that will build and run
and we got hello world back so we're
good to go like I said before we're
going to be building this project with
Git and storing our code in Source
control as we go so I'm going to go
ahead and create a new
get repository and in vs code it
highlights all the code that has changed
but not yet been committed to Source
control here in green um but I don't
want all of this in my source control
the vs code file is configurations for
my editor um that's a personal thing
that doesn't need to be in the project
itself um same with the RSS a binary we
don't want to commit the binary that
we're building or the executable file
that we're building we just want to
commit our source code so I'm going to
create a newg ignore file
and we're going to ignore the vs code
folder and the RSS a
binary and next um we're going to add
all of the secrets the like
configuration Secrets uh for our project
in a EnV file and read them out of the
file itself so uh for example one of the
configuration variables that we're going
to set is the port that the server will
run on so um I'm going to set that port
to
8,000 and I'm going to also ignore thatv
file in the G
ignore again because configuration data
is something kind of local to my machine
in production this port might be
something different so we don't need to
commit this file to Source control um
but I do want it here in my repo now at
this point I do want to pause and say if
you have no idea what a port is or you
have no idea what HTTP requests are or
rest apis are um we're going to move
fairly quickly in this project so if
you're not familiar with that stuff
again I will link down in the
description below my HTTP course that
would be a good one to go brush up on
before working on this project so now we
need a way to read this port variable
into our program so that we can use it
and the ghost standard library has a
built-in uh function called os. getet n
so it's a an exported function called
get end from the OS package and we can
get the value of a variable by its key
so in this case the key is
port and we'll get back a port
string and then for now let's just uh
let's just say if Port string equals the
empty string then we'll
say uh log. fatal so log. fatal will
exit the program immediately with arrow
code one and a message and we'll say uh
Port is not found in the
environment otherwise we'll
say
port and we'll print the port string
okay cool let's go ahead and run
that Port is not found in the
environment okay so the problem here is
that Port the environment variable
doesn't exist in my current shell
session if I wanted to add it I could
run in my in my command line export Port
equals
8000 and then run this again and then we
get Port equals 8,000 the problem is I
don't want to manually set this
environment variable every time I work
on my server I want to pull it from this
file so we're going to use a package
that allows us to grab environment
variables from a file
um and it is this package here
github.com
jooho go. EnV um that's also the URL of
the library you can paste that into your
browser uh go check it out but we're
just going to install it here
locally and that will add it here to our
go.mod and then I'm going to run go
vendor to copy that code here into uh my
my vendor folder um we get kind of a
local copy of that and we'll run um here
we're going to need to actually use it
so we do go. env. load and by default
load loads the uhv file I think we can
also optionally pass in EnV as the file
path and what's uh what's my error here
could not import no required module
provides
go let's go mod
tidy that should clean up my imports
okay
perfect
and do I need to do anything else what
are we getting here could not import no
required module oh I should probably go
mod vendor
again that should pull in the code okay
so you can see we've kind of imported
and downloaded all of that code from the
package and now I'm not getting error
any errors in my console okay so this
will take the environment variables from
myv file and pull them into my current
environment so that then I can use os.
get EnV to load the variable so to test
that let's go ahead and change uh the
port to
8080 and rerun the
server still says 8,000 so something
went wrong maybe I'm misremembering how
to use this uh use this
package let's try just go. .load
still Port
80 what am I doing wrong does it not
overwrite you know what it might not
overwrite my current session I'm going
to kill my current cell session shell
session and create a new one then we'll
do this again so now I won't have that
exported 8,000 that I had in my terminal
um run that again okay cool so now it's
pulling it from the file because I don't
already have it defined in my shell
session now I want to take just a second
and point out there are text
instructions for this entire project
over on boot. and I'll link that down in
the description below we're going to be
using a lot of text uh you know code
Snippets from those text instructions
and they'll be easier to kind of copy
and paste and grab from boot. directly
than trying to you know retype what
you're seeing me type here on the screen
now we're going to actually spin up our
server and we're going to be using the
chi router to do it it's a third-party
router uh very lightweight built on top
of kind of the the same way that the
standard library in go does http routers
and so let's go ahead and install those
now we'll do go get um github.com
gochi chai or chi I always struggle to
pronounce that one um we'll install that
and we'll install the the cores package
from the same uh the same Nam space the
chi namespace next we'll create a new
router so I'll do router colon equals
chi. new router
fact I should probably go mod
vendor have it there and then I'll do
another go mod tidy and go mod vender to
bring it
in cool so this creates a new router
object next we'll connect up this router
to an HTTP do server so serve colon
equals a pointer to an HTTP do
server and a server needs a Handler
which will be router
itself and we also need a or an address
which is just a colon plus that Port
string so in this case it'll be colon
you know 8080 cool and then we can call
HTTP do listen and
serve or sorry not HTTP listen and serve
we want to call it on the server object
so serve. listen and serve cool and
before we call that in fact I think that
returns an error so let's capture that
error and say if error does not equal
nil log.
fatal pass in the air as a
message okay listen and serve will block
so when we get to line 30 our our code
basically just stops right here and
starts handling HTTP requests if
anything goes wrong in the process of
handling those requests then an error
will be returned and we'll you know log
in and exit the program but kind of the
happy path for our code is that you know
nothing should ever be returned from
listen and serve because our server is
just going to run forever before we run
this let's just add one more kind of
logging statement we'll do log. print
line actually let's do print F and we'll
say server starting on
portent V and we'll pass in that Port
string okay cool with that let's go
ahead and build and run again so go
build see what we
get Hello World server starting on Port
880 I should probably remove should
probably remove that hello world at this
point now that we have a running server
let's go ahead and test it so I'm over
here in the Thunder client tab again
because I'm using the Thunder client
plug-in and I'm going to click new
request and we're going to make a
request to
http SL localhost right we want to make
a request to our own machine on the port
that we running on which I believe is
8080 okay with that let's go ahead and
start up our
server okay server starting on port 8080
so it should be good now you'll see I
don't have a new prompt because my my
server is still running um if I send
this get request perfect we get a 404
that's exactly what we'd expect because
remember in our code we haven't actually
set up any handlers or anything we just
have a server running so we're getting a
404 because we're trying to hit a path
in this case the root path uh and it
doesn't have anything any logic there to
handle that code if we killed our server
and ran it again we just get a
connection refused I've configured my
thunder client to actually store all of
my tests or my HTTP requests as plain
text here in the working directory uh
but I don't want those going into my
source control so I'm going to go ahead
and add that to the G ignore thunder-
tests we'll ignore everything in there
next let's add a Coors configuration to
our router so this is so that people can
make requests to our server from a
browser and we're going to be using some
fairly permissive uh configurations here
and we'll use
router. use and then we'll pass in this
cores. Handler configuration this comes
from that Coors package that we
installed earlier and let me see what am
I doing here we need one more
parentheses there and then I'm going to
go ahead and vendor this as well so go
mod tidy go mod
vendor cool so we should have all of
that code here in our vendor folder as
well I'm not going to go too in depth on
exactly what cores are you can
definitely go look that up but just to
give you a high level overview um this
configuration is essentially telling our
server to you send a bunch of uh extra
HTTP headers in our responses that will
tell browsers hey we allow you to send
uh you know requests to http or https
versions We allow you to use these
methods we allow you to send any headers
um it's just a way to say hey we're
going to allow you to do basically
whatever you want there are ways you can
tighten up this configuration for
security purposes but for now we're just
going to be running our project on our
local machine so we're going to just
open it up make it permissive to avoid
any sort of uh kind of weird testing
issues if we try to connect to our
server through a browser This Server
we're building is going to be a Json
rest API which means all of the request
bodies coming in and response bodies
going back will have a Json format so
let's create a little helper function
that will make it easier to send Json
responses so I'm going to create a new
file call it json. go it's going to be
in the main
package and the function sign is going
to look like this so we've got a
function we're calling it respond with
Json it takes as input a response writer
this is the same HTTP response riter
that uh HTTP handlers Ingo use it's
exposed by the standard Library um it
will take a code so this is the status
code we're going to respond with and
then we'll take an interface which is
just something that we can Marshall to
um a Json structure the first thing the
function will do is Marshall the payload
into a Json object or a Json string and
the way we do that is with the standard
Library so we data and error equals
json.
Marshall and we pass in the
payload so this function will attempt to
Marshall uh whatever it's given into a
Json string and it will return it as
bytes and the reason it returns it as
bytes is so that we can write it in a
binary format directly to the HTTP
response uh which is pretty convenient
if that fails for whatever
reason then what we'll do
is we'll write a header to the response
and we'll use status code 500 we'll say
something went wrong on our end right
internal service error or internal
server error um and then we'll just
return from the function and actually if
something goes wrong we should probably
log it um as well on the server side so
that we can see our own logs and see hey
we tried to do something in a broke uh
so we'll do log dot print
line failed to
Marshall Json respon
response and let's print the
response or let's print what we tried to
Marshall that's probably more more
interesting we'll use print F so that we
can interpolate that value there um next
we're going to need to add a header to
the response to say that we're
responding with Json data so we'll do
W.R
header and or not right header I'm sorry
uh w.
headers do it
header do add and we want to add the
content type key so content
type and the key will be or the value
will be application Json so this adds a
response header to the HTTP request
saying hey we're responding with a
content type of application Json which
is the standard of value for Json
responses um and then we should be able
to uh write the status code so we do w .
WR header 200 so everything went well
and then we need to write the data
itself so w. write and pass in the Json
data this will write the response body
now that we have a way to respond with
some Json data let's create an HTTP
Handler that does that so we'll do
handlers or
Handler
Readiness again this will be in the main
package and we're going to create a new
function
called Handler Readiness and this is a
very specific function signature this is
the function signature that you have to
use if you want to Define an HTTP
Handler in the way that the goh standard
Library expects so it always takes a
response writer as the first parameter
and a pointer to an HTTP request as the
second parameter and then in the body of
this Handler we can just call our
respond with Json function so we'll say
respond with Json we'll pass in that
HTTP response writer we want to respond
with a 200 status code and some some
response payload in this case all we
care about is the 200 okay status code
so I'm actually just going to respond
with an empty struct which should
Marshall to kind of an empty Json object
and now that I'm writing this I realized
that we actually made a mistake or I
made a mistake in the Json uh respond
with Json code we should pass in uh we
should use the pting Response Code
instead of hardcoding the 200 so if
everything goes right we'll use the code
given okay now with that we need to hook
up our Handler Now using the chi router
what we do is we hook up a an HTTP
Handler which is this function to a
specific HTTP method and path okay so
the way we're going to do that is I'm
going to create a new router so V1
router and we'll use that same chi. new
router to do it
and I'm going to specify V1
router.
handle handle Funk excuse me I want to
handle the slash ready
path and I want to handle it with this
Handler Readiness function okay so we're
we're connecting the Handler Readiness
function to the slash ready path and the
reason I created this new V1 router is
because I'm going to mount that so I can
do router. Mount
count to the
slv1 path okay so I'm nesting a V1
router under the slv1 path and I'm
hooking up the Readiness Handler at the
SL ready path so the full path for this
request will be slv1 SL ready and that's
just so that if we make breaking changes
in the future we can kind of have two
different handlers one under version one
and one under version two for our rest
API this is fairly standard practice
and actually I'm going to name this path
health health Z that's just a habit uh
that I'm bringing with me from
kubernetes land it's pretty standard to
have a SL Health Z path um that you can
hit to see if your server is live and
running so that's the purpose of this
Handler it should just respond if the
server is alive and running and
everything's good okay so let's go ahead
and run the server and make sure it's
doing what we'd expect so go build and
slash RSS a that starts up the server
and then we can open up thunder
client and now instead of making a
request to the root which we'd expect to
get a 404 from we'll do
slv1 healz and make that get request and
we get the 200 now here's the weird
thing if I change this to a post request
and I make that I actually still get a
200 but that's not really Our intention
the health Z endpoint should really only
be accessible by get request so I'm
going to make an update here rather than
using the V1 router. handle Funk I'm
going to use V1 router. getet and this
will scope the Handler to only fire on
get requests okay with that let's go
ahead and rebuild our
server and check again post should fail
method not allowed perfect but the get
request should still work so we have a
nice helper function for responding with
arbitrary Json now I want one for
responding with arbitrary error messages
so let's do function respond with there
um it will look very similar but instead
of taking a payload which is an
interface it will take a message string
and this function is basically just
going to format that message into a
consistent uh Json object every single
time okay uh first thing we're going to
do is say if the code is greater than
499 we're going to log a message and
that's because uh error codes in the 400
range are client side errors so we don't
really need to know about them it just
means someone's using our API in a weird
way um but we do need to know whenever
we're sering we're responding with a 500
level error code because that means we
have a bug on our end and we should
probably go fix it so we'll do log.
print line responding with 500 level
error and we'll just tack the message
itself on there okay cool after we do
that logging um we'll use the respond
with Json function uh but we'll be
responding with a
specific uh
structure of Json uh so let's go ahead
and Define that as a struct so type um
air
response is a struct and it has one
field error which just a string and
we'll add this Json tag to just say this
the key that this should Marcel to is
error so in go we typically take a
struct and add these Json reflect tags
to it to specify how we want this json.
Mar function or on the other side the
json. unmarshal function uh to kind of
convert this struct into a Json object
so in this case we're saying I have an
error field it's string and I want the
key for the field to be error so this
struct will Marshall into a Json object
that looks kind of like uh like
this
error you something went wrong
right it wouldn't have actually wouldn't
have that but it would look like that
okay and we'll see that in just a second
okay so now we get to respond with Json
we pass in the response writer a the
same code that we were given and then
we'll just respond with an error
response and the error message will be
the message that we were
given okay let's hook this up to another
Handler so here we can do V1 router.
getet we'll create an error endpoint and
oh I need I need an actual Handler so
we'll create a new one called
Handler
error Handler error and we'll respond
with an error instead of passing in an
empty struct we'll
say something when
wrong and we'll respond with a 400
status code client error right okay now
we can hook up this error
Handler here it will only work on get
requests that seems reasonable and
basically it's just going to call that
respond with error function so it'll be
a good way to test
that okay let's go ahead and rebuild the
server nope what did we screw up
routing pattern must begin with Slash ah
let's go fix
that so you can see here we've got SL
Health zv1 we need to start these with a
slash it's just the way the chai rou or
the chi router
works cool um let's go open up the
Thunder client and send a
request to the slash errror
Handler cool we get the 400 bad request
status code and this is that Json body
um so every single time that we need to
return an error from our server now we
can just use this function and it will
always use this consistent error format
which is great because we can throw this
in our documentation and just tell all
of the users of our API hey this is what
you should expect when something goes
wrong now that we have a little bit of
our boiler plate set up I'm going to
take the opportunity to commit all of
this to get uh so that I don't lose it
um I will say that I generally recommend
committing the vendor folder so you can
think of the vendor folder kind of like
the node modules folder if you're
familiar with JavaScript land and in
JavaScript you would never commit it
it's way too big um but in go we
typically don't have all that many
dependencies so it's actually perfectly
fine to commit the vendor folder in most
scenarios and I'd even recommend it so
I'm going to go ahead and add that and
commit
it we'll say
boilerplate or http
server
complete for this project we're going to
use postgress as our SQL database it's a
production ready database in fact it's
the one I used to build
boot. you're going to need to install
postgress on your local machine make
sure that the postgress server is up and
running and that you have a client
installed that you can use to make kind
of oneoff SQL queries against it I have
detailed instructions on how to do all
of that in the text instructions for
this project over on boot. so again go
check those out if you need to figure
out how to install postgress locally and
get a postgress client up and running on
your machine I use PG admin so that's
what you'll see me using in this
tutorial so if you followed those
instructions then you should have a
postgress server running on your local
machine and a postgress client installed
again I use PG admin that's what you're
seeing here on the screen okay so
because postgress is running locally I
have this Local Host server here in PG
admin that I've connected to again
that's the postgress server running on
my own machine and under databases I
have kind of the built-in postgress
database but I want to create a new
database that we're going to use for
this project so um in this case I'm just
going to name it uh RSS
a and we'll create that
database and then here within the RSS a
database as long as the uh kind of icons
are gold then you're connected and
everything is working at least up to
this point let's run a quick query
against the database just to really make
sure everything's working so I'm right
clicking here on the RSS a database and
I'm going to click query
tool and from this tool I should be able
to just write some raw SQL so I'm going
to go ahead and do a
select version this should just return
the current version of postgress that
I'm using I'm on version 14.7 and as
long as you're on something
14.7 or newer you should be good to go
now it's important to keep in mind here
that PG admin is just a client for
interacting with an SQL database right
we're able to write raw SQL code here
and run it against our database server
if you think about it kind of in an
analogous sense PG admin is basically
just the same thing as the Thunder
client where the Thunder client is a
client for running one-off HTTP requests
against our server PG admin is a client
for running oneoff SQL requests or SQL
queries against our database directly
next we're going to install two command
line tools that will allow us to work
with SQL databases from our go code much
easier now these aren't fully-fledged
orms if you're familiar with that term
these are kind of lightweight libraries
that allow us to work with SQL databases
using the standard library and just sort
of streamline the process for us the
first one is called
sqlc and again you can find all of these
commands in in the text instructions
over on bootd so be sure to be following
along over there but we're going to use
the go install command to go grab uh
sqlc and install it into our command
line once that's done you should be able
to just run
sqlc version to make sure it's working
next we'll install Goose the same way so
go install um and then the installation
path for goose again uh that link is
over in the text instructions and then
you can make sure that goose is
installed working correctly by typing
Goose Das version the great thing about
sqlc and goose is that they work based
on Raw SQL there's no kind of fancy
query language that's unique to those
tools we can just write SQL queries and
we're going to store all of that in our
repository so I'm going to create a new
folder is called SQL and in there I'll
create a new directory called schema and
this is where we'll store all of our
table definitions um or more
specifically our migrations so uh we'll
start with a users table and the way
Goose works is it runs the migrations in
order so we're going to start with
a01 migration and we'll call it users.
SQL from a very high level the way that
database migrations work is they have an
up and a down statement so for example
here we're creating a users table the up
statement will just create a new users
table and the down statement will delete
that same table so any down statement
should just undo the operation of the up
statement and that just makes it easy to
roll back changes to our database schema
if we ever need to the goose command
line tool Works based off of SQL
comments so we'll start with a comment
Das Dash plus Goose
up and dash dash plus goose down and
then anything we type here uh will be
considered an up migration and anything
here will be a down migration so let's
start with the up migration it's going
to be
create create table
users and the first field will just be
called ID it'll be a uyu ID a
universally unique identifier um I
prefer uu IDs to integer primary keys
for a number of reasons um I'll link a
blog post down in the description below
um and that's just going to be a primary
key uh next we're going to need a
created at which is a a
timestamp not null we must have must
have a created at must have an updated
at same
thing and then a user will also have a
name and we'll just make that a text
field again let's say that's not null I
need to remember to terminate my SQL
statements with a semicolon and for the
down M migration um it's pretty simple
we'll just drop the table so drop table
user users all right let's run our
migration but first we're going to need
to be able to connect to our local
database from our program and from our
command line so uh very first thing is
we'll need a DB
URL and we'll set it equal to the URL
that we use to connect to our local
postgress server so this isn't to
connect to PG admin this is the same
connection string that PG admin uses to
connect to the database server we want
to go directly to the database so it's
going to look something like this
postgress is the protocol so colon SL um
again this is just a URL um and then we
have the authentication part uh which in
my case is Wags Lane because that's the
uh user on my machine um and then colon
and then password if you have a password
for your local database this is where it
goes I actually did not set one up
because it's just my local
database and it's going to be at Local
Host col5
432 which is the standard port for
postgress and the last part of the URL
is just going to be the database name uh
that you created so in my case I believe
it was RSS a okay so your url will
should look very similar to this with
maybe you know the username the database
name um something like that could be
could potentially be swapped out on your
machine okay to run our migration here
I'm going to copy I'm going to copy this
database URL
and then I'm going to CD into this
directory so CD SQL
schema and then from here I can run
Goose
postgress so I'm telling I'm telling
Goose that hey I'm using a postgress
database and then I'll paste in my
connection
string and type up so this will run the
up migration a nasty error here turns
out I forgot some commas we need to
separate all of these field names with
commas cool save that file let's try
again so we got okay 001 users. SQL no
more migrations so that should have run
let's check PG admin to make sure that
it worked so now over in PG admin under
my RSS a database I should be able to
come into the schemas tab the tables tag
and I can see here that I've now have
two tables Goose DB version so this is
an automatic table created and managed
by goose and then I've got the users
table that I just created let's go ahead
and do a select star from users and we
should just be able to see uh those
column names come back now let's make
sure that the down migration works as
well go ahead and run the exact same
thing but this time down instead and you
can see that it down migrated the same
file now over in PG admin if I
rightclick on tables and click refresh
you'll see the users table's gone and
this query should fail now okay so let's
reup migrate to get that database table
created again and then the interesting
thing about migrations is you can rerun
the same up migration and you won't get
any errors because Goose knows that
you're already migrated up to the most
recent version of your migrations now
it's time to write a query so we're
using sqlc to handle our queries and
Goose to handle our migrations so to get
sqlc set up we need to create a new file
in the root of our project called sqlc
do yaml I'm going to paste in this
configuration here basically it's just
telling sqlc what version we're using um
what database engine we're using and
where we're going to store our queries
the raw SQL for our queries are going to
live in the SQL directory under a new
subdirectory called queries we've
specified that here right and here I'm
going to create a new file I'm just
going to call it users.
SQL and again this is where the SQL will
live and the way sqlc works is that it
takes the SQL statements and it
generates go code Type safe go code that
matches the SQL every sqlc query starts
off with a comment that starts with its
name so name will do a create user
statement and it returns one record so
we're saying I want a new function
called create user oops and it's going
to return one user that statement will
be insert into users uh ID created
Created at updated at and
name
values dollar sign
one dollar sign 2 dollar sign 3 dollar
sign 4 okay so what's this nonsense
right in sqlc each dollar sign number is
interpolated with the parameters for the
function so this statement will create a
new function called create user
with four parameters and the first
parameter will will go in right here the
second one the third one the fourth one
Etc so it allows us to create queries
that take arguments as input and then
we'll end the query with just return
returning
returning star okay we want to create a
new user and return that record right we
we expect one record back now let's use
sqlc to actually generate the go code
for this query we always run sqlc from
the root of our package rather than
within the query's directory itself um
and the reason that works is because we
have this sqlc do yaml file at the top
level okay so if everything was written
correctly we should be able to do sqlc
generate and what happens is it goes and
reads that query and it also reads our
table
definitions which we've specified here
right SQL schema so it knows the shape
of our tables and it knows the query we
want to create and it can go
automatically generate all of this go
code in the internal SL database package
now we need to actually use the database
in our go code so here in main.go I'm
going to create a new struct called API
config and it's going to hold a
connection to a database now this
database. queries type is actually
exposed by that code that we generated
using sqlc so you can poke around
through this package and kind of get
familiar with the generated code we
never manually update this code that's
generated by sqlc it's completely
managed by sqlc we're just going to
write raw SQL to generate this code okay
uh next thing we need to do is import
our database connection so here in EnV
we have our DB URL and we need to
grab that um and pull it into our
application we're also going to need to
disable SSL mode so SSL mode equals
disable um and it just this just tells
our code hey we don't need to be
connecting to our local database um
using encryption uh we kind of trust our
local database so we'll parse that as a
string so I'll do DB
URL and if the database URL is not found
then
we'll we'll report a message uh or we'll
log an error message and exit after that
we need to actually connect to the
database so the ghost standard library
has a built-in SQL package we can do SQL
doop the driver name that we'll be using
is just
postgress and then we can pass in the
connection string and this will return a
new connection and an
error and again if there's an
error we'll just go ahead and log a
message and
exit can't connect to
database now this is kind of a weird
quirky thing about how go handles
databases but we actually need to import
a database driver into our program um
but we don't actually need to call
anything from it so the sqlc docs
mention this but basically we just need
to include this line at the top of our
program and we do need to import it so
I'll do a goget um on that
libpq and we'll import it using that
underscore just to say include this code
in my program um even though I'm not
calling it directly okay with that there
uh now we should be able to create a new
API
config and let's just call it API
CFG and it takes as one of its Fields a
DB
where am I at I think I scrolled too
far BB oh and I should probably go mod
tidy and go mod
vendor so that I stop getting weird
errors in my in my vs
code okay uh this API CFG takes a
database. queries but if you look here
we don't have a database. queries we
have an SQL DB so we actually need to
convert it into a connection to our
package and we can do that with
database. new and we pass it as input
the connection and we'll get back
queries
eror here we can say if er do not equal
nil and pass in the queries
to the
struct no did I do that wrong maybe this
doesn't return an
error mismatch two variables but
database. new returns one okay cool so
this actually can't fail it's just a
it's just a simple
conversion we could actually even just
do this it's probably
easier great now we have an API config
that we can pass into our handlers so
that they have access to our database
let's write that create user Handler
okay so I'm just going to copy paste
this Handler Readiness and change it to
Handler
user and we'll update this to say
Handler this will be the create user
Handler now here's the interesting thing
about HTTP handlers in go the function
signature can't change but we do want to
pass into this function an additional
piece of data we want
to add this API config so the way we do
it is by making this function a method
so we do API
CFG is a pointer to an API config so our
function signature Remains the
Same right it still just accepts these
two parameters um but now we have some
additional data stored on the struct
itself uh that we can gain access to and
let's hook up this create user Handler
in
main so we'll add it to the V1 Handler
we'll do V1 router Dot
post we want this to be a post request
to/
users and we want to use
the create Handler create user method
which we defined on this struct so we
can pass in API CFG do Handler create
user and now our Handler will have
access to um to the database okay
cool this Handler needs to take as input
a Json body it should
expect some parameters so we'll do type
parameters is instruct and I think for
now we just need a
name and we need to parse the request
body into this struct so we'll do json.
new
decoder and r.
body okay and this respond or this
returns a decoder
we do decoder do
decode and we want to decode into an
instance of the parameter struct so
we'll do prams is an empty parameter
struct and we'll decode
into a pointer to parameters and this
returns an error if anything goes
wrong if there is an error then we
should use that Handler function that we
made earlier respond with error and say
something like well we need to pass in W
um if something goes wrong here it's
probably a client side error right so
I'm just going to pass in a
400 and we'll
say
[Music]
um let's see error parsing
Json cool and then we'll return because
we're done at that that point if there
is an issue okay uh otherwise we have
access to a name so we can use our
database to create a user so we do API
CFG do
db. create user now this is the method
that sqlc generated for us right because
it it read our create user SQL and it
created a create user function for us
right and it created the parameters as a
struct so that's pretty
convenient uh let's see how this works
so create user accepts a context and
some create user pram so I'm going to
use uh I think
it's CTX no r. context there we go so
that's the context for this request and
then we pass in database. create user
prams this is struct and it should have
yep all of our uh types that we need to
pass pass into the create user function
okay so uh first things first an
ID the ID is a
uuid um and this is the first point at
which I think we've needed to use them
so we're going to have to import this
package so github.com gooogle uid this
is a very uh well-known uuid package in
go we will go get
it with that installed we should be able
to do you uu id.
new and that will just create a new
random uuid and if you weren't familiar
this is what a uu ID looks like in
string form it's basically just this
really long random uh bit of I mean in
this case represented as text that we
can use as a primary identifier for
every user so every user will get their
own random
ID cool um I should probably go mod tidy
go mod vendor
it's just good practice every time you
install a new package to make sure you
vendor it and go mod tidy kind of cleans
up any unused Imports um and resolve
some issues
there okay uh created at I'm going to
set to just time. now.
UTC created now and then updated at
should represent the last time it was
updated uh which would also be now right
because we're creating something new and
the user's name will just be pam. name
right it's whatever was passed in
to this HTTP request in the
body oh and I just am now realizing that
I messed up this Json tag should look
like
this okay cool um so create user should
probably return an error yep returns a
new user and an
error again if there was an error
creating the user we'll want to respond
with an
error and we'll
say good
create
user uh 400 seems fine and then we'll
actually respond with the user object
itself okay database. user and see how
that goes well let's let's actually take
a look and see what does a database.
user even look
like I'm
curious yeah so all these I mean all
these fields are exported so they should
Marshall to J on just fine let's go
ahead and run this and see and see what
we get before we run the code though it
looks like I've got a couple little
things to resolve here so errors is
already defined there and then here oh
I'm messing something up we need to pass
that in we need to interpolate that cool
oh and here as
well percent V because those are errors
okay cool um now let's go ahead and run
build and run the code so go build
and run RSS a
okay Ser server has started so let's go
ahead and open up the Thunder
client and now we'll be sending a post
request to the users's
endpoint and we'll be sending in a Json
body oh me grow this just a little
bit and we need to specify a name I'm
going to create a new user called Lane
and let's see what happens couldn't
create user database Wags Lane does not
exist it looks to me like I probably
messed up my connection string let's go
take a look at
that so here in
EnV yeah okay we forgot to or I forgot
to add um the name of the database here
at the end so uh we we need to do slash
name of database all right let's try
that again let's rebuild the server
and resend that request cool we got a
200 response it looks like that is a new
random ID great created at update at and
the name next just to make sure that the
record actually was created in the
database server itself I'm going to pop
back over here to PG admin um refresh my
tables there's our users table and rerun
this select star from users perfect
looks like we've got one record in here
with all of the data that I would expect
now I want to make one more optimization
to our code here you can see in this
Json response that the Fe the key names
in the Json object are the same as the
exported key names here in the user
struct in the database package now we
can't change this struct manually um
again this is generated by sqlc so what
I think we should do is instead create a
new models folder models. go
in the main
package and here we'll create our own
user type so type user struct and it
will be nearly identical so let me go
grab this one be nearly
identical the only difference at this
point is that I'm going to add Json
tags so that I can specify you know what
these names should be and we've been
using kind of this snake case convention
so I'm just going to stick with that so
updated at and name and I'm going to
create a function uh we'll say database
user to user and it will take a DB
user and return a user and all this does
is return a new user struct where uh
we've kind of populate it with all of
the all of the stuff from the database
user so again the purpose of this is
really just I want to own the shape um
that's being returned over the wire
right on our HTTP responses um and now I
have the power to configure that easily
within within my
application so we'll go ahead and just
uh paste these in
here okay and then in my user Handler
rather than responding with the database
user I'm going to respond with our
user cool let's rerun and build
that and let's run our query again now
remember we already have a user in our
database so I'm going
to create a second one uh let's call
this one
Rob and this time you can see we have
those snake case
keys and again I'm going to go check in
PG admin to make sure that Rob is there
perfect now we've got Lane and Rob and
you can see they have different randomly
generated IDs and their timestamps are
slightly different we're going to be
using API keys to authenticate our users
on This Server the nice thing about an
API key is Not only is a little more
secure than a username and a password
but because it's so long it also serves
as a unique ID for that user so we don't
even need a combo of username password
we can just use the API key in order to
kind of uniquely identify people so we
need to run a migration that adds a new
field to the user's table so that we can
store their API Keys now we've already
created this migration that creates the
users's table and we don't want to
modify this because it's generally a
really bad idea to go modifying your
existing migrations instead we create a
new one so I'm going to create a new one
and we'll call it 02 because we want it
to run after uh the first migration and
again Goose uses these numbers to know
in which order it should run uh the
migrations and we'll call it users API
key and the migration statements are
going to look a little bit different
okay so the up statement is going to be
an alter table so alter table users
We'll add a
column and we'll call the column just
API uncore key it's going to be a varar
so
varar 64 now the difference between
varar and text um at least for our
purposes here is that the varar is
exactly 64 characters long so we're
saying we want our API keys to be 64
characters long and we want those API
keys to be unique no two users should
have the same API key um we also don't
want them to be null and we're going to
set a
default a default API key and this is
important because if we didn't set a
default we'd run into an issue when we
run this migration remember we already
have two users in our database currently
so if we just try to add a column that
has these unique not null constraints on
it then what's the SQL database going to
do how is it going to generate new API
keys that are unique and not null
typically it would just default the new
um you you know the field in the
existing records to null but because
we've said they can't be null and they
must be unique we need to provide a
unique unique default for every new
record or for excuse me for every
existing record in the database so the
default value that we need to add again
needs to be unique for every person so
we're actually going to have to use some
random number generation so that we can
generate a unique API key for every user
and this is the snippet of code that
does that again you can go grab this in
the text instructions for this project
over on bootd but let me explain
basically what it's doing we're
generating
some random byes and then we're casting
it into a bite array and we're using the
shaw 256 hash function to get kind of a
a fixed size output so we're saying take
a a big random slice of bytes hash them
with straw 256 so that we get a fixed
size output and then encode it in
hexadecimal and that's so that we get 64
unique heximal characters this makes
more sense when we actually run the
query and you see what the output looks
like and then as far as the down
migration goes we just need to alter
table users and
drop column API key again down
migrations should just undo whatever was
done in the up migration let's go ahead
and run this migration so I'm going to
need to change directory into
SQL uh
schema then we'll run goose postgress
then let me go grab the connection
string now we do need to peel off this
SSL mode disable Goose doesn't need that
um just our code needs that so I'm going
to grab the rest of the string and we'll
run an up
migration cool so it looks like it ran
successfully let's go see if those
default values look good run the select
star statement and there you can see the
new
AES so big old heximal strings that
uniquely identify every user and should
be kept secret by the user because just
the API key is enough to authenticate
the user now that we've got our
migration and we've updated our schema
we actually need to go update our query
right we need to be creating new API
keys for new users so let's go update
our create user function it should now
accept an API key as the last
parameter and pass it in here
actually you know
what if we do it this way we're
basically telling our application code
hey you need to go generate an API key
in the same way that we generated it
here in our SQL I think it would
actually be easier what if we just take
this and plop
that in
here right so now we'll use we'll use
SQL to generate the new API Keys we
don't even need to update the function
signature of our create user function
cool so the SQL will just handle the
creation of new API Keys every time a
new user is created all right now we
should be able to
run sqlc
generate insert has more Expressions
than Target columns let's
see oh right sorry we we do still need
to pass in the API key as the column
name um the difference is because we're
not using dollar sign 5 our function
signature won't change this got a little
confusing I was reading it like a
function signature even though it is it
is SQL okay run that again it went off
without a hitch you can see it updated a
few files in our database package now we
should be able to go use that in our
code but before we test our server let's
add one more thing let's give us a way
to get a user so we'll create a new
function and this one we'll call get
user by API key it should return a
single row
and it's going to be a select statement
so select star
from
users
where API key equals dollar sign
one and we'll run sqlc generate again to
generate the code for that query you can
see it created it here so in our Handler
user function we actually don't need to
make any changes to our create user
Handler right right we didn't change the
number of parameters that we need to
pass in for the API key it's handled
kind of under the hood by the SQL query
but we do need a new Handler for getting
users so let's go ahead and add
that so I'm going to copy paste that do
Handler get user by API
key maybe we can just let's just
simplify this let's just call it Handler
get
user now this Handler is going to look
very different
this is an authenticated endpoint so in
order to create a user on our API
basically to register a new account you
don't need to have an API key but if you
want to get your own user information
you have to give us an API key first
this isn't going to be the only
authenticated endpoint or the endpoint
that you can only do if you're logged in
um so I think it makes sense to kind of
abstract the logic for getting a user by
their API key into a package so under
the internal package here where we have
our database code I'm going to create a
new package and we'll just call it
o and in there I'll create a new file
we'll call it o.o and this whole package
will just be called
off now the only function that we care
to export in this off package is going
to be this one called get API key and
its purpose get API key key will'll say
it
extracts an API key from the headers of
an HTTP request so it's going to go into
the headers look for a specific header
and see if it can find the API key if it
can it'll return it otherwise it will
return an
error now as the authors of This Server
we get to decide what we want the
authentication header to look like so
I'm just going to
say um example
let's expect an authorization header so
the key of the header will be
authorization and the value will be API
key and then some you know like insert
API key here okay so we're looking for a
header of this
format so first let's look and see if we
can find a value associated with the
authorization key um we're just using
the HTTP standard Library here so we can
do headers do
getet authorization and this should
return let's see a string okay so the
value associated with this with this
header key now if value is the empty
string then we can just say uh turn
empty string and an error say errors.
new no authentication
info found so otherwise we have a valid
value so we could do something like this
uh
vows strings.
split
vow okay so strings. split takes a
string as input and a delimiter so we're
going to say we want to split this
string here right the the value given to
us uh by the auth authorization header
uh we want to split on Spaces okay so
next we can say if the length of
vals does not equal
two then again we can return an error
saying like you know maybe
malformed malformed a header right
because we're expecting that the value
of this key is two like two specific
values separated by a space the first
should be API key and the second should
be the API key right the first should
just be the string API key and the
second should be the actual API
key okay so if the length is wrong then
uh next we should probably check and
make sure they typed this in correctly
so we can say uh if vals at zero does
not equal API
key malformed offhe
header we could say malformed first part
of offhe header okay
otherwise we can just
return B at one and no error
right because by that point we sure that
all all this part was correct and we're
extracting just the API
key okay what did I screw up here
errors. new oh right you're not supposed
to capitalize errors in go that is a a
linting error
a stylistic error Okay
cool so now we've got the get API key
function we can go ahead and use this in
our get user Handler so let's go ahead
and grab that API key so API key and
error o. getet API key and we pass in
the HTTP headers so r.
header perfect if there is an error
let's handle it
we can just respond with an error
saying off
error and 400 we should probably do like
a 403 and in fact now that I'm thinking
about HTTP codes creating a user
probably should be a 2011 instead of a
200 like you probably won't run into any
issues for using a 200 um but 201 is the
created code so it's like a little more
correct uh if you're looking at it from
kind of a rest full HTTP standpoint and
then 403 um is one of these kind of
permissions errors so that should be
good okay now that we have an API key we
can use our database query that we
created. get user by API key again we'll
pass in the requests context and the API
key I haven't really touched on this
yet in go um there is a context package
in the standard library and basically it
gives you a way to
track something that's happening across
multiple go routines and the most
important thing that you can do with the
context is you can cancel it so uh by
cancelling the context it would
effectively kill the HTTP request I
don't want to go too much into detail on
how all of that works here um you could
definitely go read up on it um but for
now just make sure that it's important
to kind of use the current context so
every HTTP do request has a context on
it and you should use that context um in
any calls you make within the Handler
that requires a context just in case uh
kind of cancellations happen Okay cool
uh that returns a user and an
error again if there is an
error Let's do let's use a better
uh string here
maybe couldn't get
user and this one let's just go with a
404 or a 400 for
now cool um and then we can respond with
Json this time it will just be a 200
code and again we should cast that
database user to the user uh model that
we defined here right with the the
nicely formatted Json tags and that
should be good okay let's hook up our
get user
Handler to our
router so here we'll do V1 router. getet
so same path SL
users but we'll be hooking up the get
user Handler to the get HTTP method so
again same path different method all
right let's test our new
endpoint um first I'm going to go ahead
and rerun sqlc generate cuz I can't
remember if I've done that and then
we'll build and run the
server okay with that running let's head
over to thunder
client and first let's create another
new
user so Json body me minimize this a
little
bit um let's keep create a new user
we'll call him
Rand and it'll be a post request that
looks good
okay response came back Rand was created
and this is rand's ID ah we screwed
something up we are not responding with
the API key let's go update
that so here in our model I believe it's
because we're not casting so we need API
E
string
bason API
key and then
here need to do that conversion so this
just getting dropped because we weren't
we weren't setting the API key
anywhere okay uh let's
rebuild and we'll create a new user
let's call this one
Joe Okay cool so Joe was created and it
actually returned the API key perfect
now let's go ahead and I'm going to
create a new request
this one also to Local Host
8080 slv1 users but this one will be a
get and we're going to add some headers
or specifically one header right we're
going to add our authorization header
and its value will be API
key and then the actual API key just
paste it in
there okay let's go ahead and run that
and it it's returning Joe it's good to
test failure cases too so let's just see
what happens if
we update this header and let's just
like let's just make something broken
let's just remove a section of the API
key and see what
happens cool we get a 400 batter request
couldn't get user SQL no rows in the
result set so essentially user is not
found perfect so we've got our users set
up and our authentication system now
it's time to actually get to some
business logic right this this is an RSS
feed aggregator so we need a way to
store feeds let's create a new schema or
rather a new migration in our schema
folder this will be O3 we'll call it
feed feeds dsql now this is going to be
a create table migration right we want
to create a feeds table so we'll do
create table
feeds now a feed and the drop will also
drop the feeds table a feed has an ID
just like a user um it also has a
created at and an updated at and it also
has a name like all of that is actually
very similar um what's unique about a
feed is it has a
URL uh which is text as well that is
unique and not
null and it also has a user
ID which
references sorry it's a user ID is a uu
ID which references
users ID and we'll also add the on
delete
[Music]
Cascade essentially what this does is it
says we have a user ID stored in our
feeds table that
references the ID of a user in the user
table right so this is this is
relational data this is a relational
database um essentially what this means
is if we try to create a feed for a user
ID that does not exist in the users
table we'll get an error uh which is
what we want because we don't want feeds
to be able to exist without a user who
created them and then this on delete
Cascade bit just says when a user is
deleted I want all of the feeds
associated with that user to be deleted
automatically it will Cascade and delete
all of them and let's run this migration
so I'm going to hop into the SQL schema
directory and from here we can run goose
postgress postgress and Gra my
connection screen again we don't need
the SSL mode stuff for goose grab the
rest of it and up cool now over here in
PG admin I can do a select star from
feeds and make sure that that table
exists with those fields next we'll need
a query to create a new feed so I'm
going to go ahead and copy this queries
file update it to feeds then we'll
delete this one because we don't need it
and we'll create a create
feed and insert into feeds ID created at
updated at name there will not be an API
key but let's see we need a name URL and
user ID okay name
URL user
ID we won't be generating an API key
here instead all of these values I think
we'll just be passed in from our
application code so 1 2 3 4 5 6 we need
six parameters for this function five
and six and then we'll just return uh
the entire feed row after it's done
being created and then I'll just
navigate back to the root of the
project and run SQL sqlc
generate to create the code for that new
query next we're going to create a new
Handler that will allow users of our API
to create a new feed here's the thing
that hand is also going to need all of
this same
logic that we have in the get user
Handler right we'll need to grab an
authentication uh token or an API key
from the authorization header fetch the
user and then use that user in the
Handler and rather than copying and
pasting uh this what 10 lines of code
into every Handler that's authenticated
instead we're going to build some
middleware to kind of dry up the code
right um let's go ahead and do that so
I'll create a new file I'm going to call
it
middleware
of.go art of the main
package and here we're going to define a
new type and it's our own custom type uh
I'm calling it off Handler and you'll
notice it looks almost exactly like a
regular HTTP Handler the only difference
is that it includes a third parameter it
has a user associated with it so if you
think about this um it makes a lot of
sense uh for any authenticated Handler
to accept three parameters where the
third one is the authenticated user now
the problem with this off Handler type
that we created is that it doesn't match
the function signature of an HTTP do
Handler Funk right uh those functions
with just the response writer and the
request as the only two parameters so
what we're going to do is create a new
function called middleware off uh that
works it's a method on our API config so
it has access to the database um but it
its job is to take an off Handler as
input and return a Handler Funk so that
we can you know use it with the CH
router okay let's Implement that the way
this function will actually work is
we're going to return a closure so we're
returning here a new Anonymous function
with that same function signature as
your normal HTTP Handler funk the
difference is that as we Define this
function we'll have access to everything
within the API config so we'll be able
to query the database so we can
basically just go rip out the code from
our get user Handler and paste it in
here right we're going to go get the API
key from the request well from the
request headers at least and then we can
go ahead and grab uh the user using that
API key
right so we'll have access to the user
here in the function finally all we need
to do is run the Handler that we were
given with the response writer the
request and the user right so by the
time we get to actually calling the a
Handler we're able to give it an actual
user from the database and this is
really great let me show you why so now
that the middle middleware o function
exists we can remove all of this code
from the get user Handler we can update
the get user Handler to accept as
input a database
user user database.
user look at how clean this function
becomes right now it's just now it's
literally just one line cool and now to
hook it up you'll notice we have an
error over here we just need to call our
middleware off
function apic CFG do middleware
off we call this function to convert the
get user Handler into a standard HTTP do
Handler funk I kind of move fast through
that hopefully it all make sense though
basically we're just calling the
middleware off function first to get the
authenticated user and then we're
calling that call back the the get user
Handler the nice thing is now we'll be
able to reuse that middleware across
many different HTTP Handler functions so
now let's create the create feed Handler
I'm going to go ahead and just copy this
Handler user change it to Handler
feed and for now we just need a create
function so I'll delete this get and
we'll call it
Handler create
feed and remember it's an authenticated
endpoint so we can have it accept the
user directly so user database. user
we know who's creating the feed by the
time we get to this function which is
awesome all right in order to create
create a feed use our new create feed
function which takes create feed
pams and create feed pams have ID
created at updated at name that's all
the same the difference is a URL and a
user
ID so
URL and a user ID which is a U ID uh
which actually exists already on the
user object so we can just do user. ID
okay um what do we want as input we need
a URL so we also are going to want a
prams
DOL so we want the user that's creating
a new feed to be able to just send us a
name and a URL and we'll go about
creating the entire uh kind of you know
feed object in the database or feed Row
in the
database so this is what our parameters
should look like uh what error am I
running into here cannot use API cf.
create
feed no new variables on left side of H
that's odd create feed should return a
feed let's take a look at that
definition yeah it does return a feed
what am I messing up
here oh first of all that shouldn't be a
user that should be a feed okay that's
the problem I was over I was trying to
overwrite the database. user type with a
feed type that won't
work okay so we're creating a feed we're
generating a new uid that's great we're
using the current time perfect um this
is getting messed up uh variable of Type
U id. uid as uu id. null U ID ah okay I
see the problem the create feed
pams except a uu id. null uuid that's a
problem we don't ever want a u the null
uuid type from the uid package is a
nullable uuid but we don't want it to be
nullable because we expect that every
feed will be created by a user so let's
go update
our our uh I think it's our is it our
migration let's go look schema
feeds yeah user
uid not
null cool it doesn't need to be unique a
user can have multiple feeds but it
should never be null all right um with
that we're actually going to need to
go uh rerun our migrations so
SQL um
schema and we'll do a down to drop the
table
down and then back up to create the new
table with the proper
schema and then we should
rerun SQL C
generate okay did that
work create feed create feed pams looks
like that error is gone it's now just a
uuid type perfect now at this point we
have a valid database feed should
probably update this error so that it
actually says feed um and we want to
return it um in our HTTP response
trouble is remember we don't want to
just directly return the struct let's go
create a new model for a
feed so we'll do
type feed and then I'm going to go just
copy the types from here and we'll use
those and we'll make our own Json tags
for the type
user
ID
URL name updated at and create at okay
and then we'll want
a what is it database
database feed to feed
and we'll return a
feed all of this should be pretty
straightforward and again this just
gives us more control right now we in
our code that's not generated by SQL c
um are able to Define you know what the
shape of the response will look like if
for whatever reason we needed to store
some data in the database but never
wanted to respond with it in our Json
API we could make those changes here in
this struct okay so now we've got
database feed to feed we'll call
that and now we should be good to go all
right let's test out our new Handler so
going to rebuild our server and run
it and over in the Thunder client let's
see so we just created a new user Joe so
we have our authentication key here or
our API key going to do a new request to
http
localhost 880 slv1 feeds ah now that I
type this out I realize that we never
hooked it
up let's go hook up that feed so feed
Handler Handler create feed we need to
go paste this in
to the main function so this one will go
under slash feeds because we're creating
a resource we're going to use a post
request Handler create
feed okay that should be hooked up now
let's uh restart our
server and over here again I need to
grab that API key all right
what do we want here post
request Local Host 8080 V1
feeds we do need to authenticate again
so qu
headers
auen
authorization API
key paste in that API
key okay uh what do we send in as the
body
let's take a look at our Handler again a
name and a URL okay so name now remember
this is this is this is not a person
this is a feed right and a feed is a URL
that kind of links to an RSS feed out on
the internet so this one I'm going to
put just uh Lanes Lan's
blog and then the URL is going to be
https
tags lane.
deev SL
index.xml so in case you're not familiar
with what an RSS feed is let me just
show you really quick um I'm here on my
blog Wags lane. deev and if you click
RSS up at the top it'll take you to my
RSS feed now every RSS feed um will have
a different URL it's kind of up to the
author of the blog or the podcast what
that feed URL is but you can usually
find it by poke looking around on their
website so in my case um it's just
waglan dodev index.xml and it will look
something like this if you open it up in
a browser it's basically this structured
XML document that describes what each
post on the blog says at least from a
high level it'll usually have something
like a link to the post maybe a short
description um basic stuff like that
again podcasts also work on the same RSS
structure so for testing you can use my
blog or if you know of any other RSS
feeds um out on the web you can use them
so now that I've pasted in that URL uh
let's go ahead and create that
feed and what do we get
back cool the feed's got a new URL
created at updated at the name and the
URL seem to have persisted correctly and
that is the user ID associated with the
API key that we use to create the feed
next we're going to add the ability for
any user to get all of the feeds in the
database this is not an authenticated
endpoint okay so we need new query I'm
going to go ahead and use the same file
um this query will just be called get
feeds and it will return many rows
instead of just one all right and this
one will be uh
select star from
feeds super super simple query here
we're just going to go grab all the
feeds and return them okay um from there
we should be able to sqlc
generate and let's go hook up that
Handler I'm going to use the same uh
Handler feed file U but this one will be
a little bit
different we going
to call this one Handler get
feeds it's not authenticated so we don't
need to pass in a user and it doesn't
even take any parameters right it's just
going to go get all of the feeds so um
apic
cfg.init' to return all of the feeds
this is not a single feed this is now a
slice of database. feeds so not only do
we need to return them but we need to
actually convert them so let's go update
our models a little bit let's create a
new
function this one will be database feeds
to
feeds and the difference is it will
accept a slice of database feeds and it
will'll return a slice of
feeds let's let's do this slice feed
we'll create a new empty slice of feeds
and then
four DB
feed range DB
feeds feeds equals
append
feeds database feeds or database feed
and return feeds sorry not we don't want
to append it directly we have to we have
to call our conversion function okay so
this function will just iterate over all
of the database feeds one by one
converting them into our new feed
structure and then returning them cool
now here we can use that
function to do that
mapping great let's hook up this Handler
so in main.go we'll create a new entry
here this is going to be a get request
and it's not
authenticated it will be
Handler yet
feeds cool let's regenerate I can't
remember if I generated my SQL C so I'll
do that couldn't hurt and then we'll
build and run the
server okay with that running let's go
ahead and create a couple more RSS feeds
so
here update the
body of my request sorry I'm so zoomed
in so that you guys can see and it just
makes it
hard um no this was to create users this
is users here's
feeds okay let's let's just add the same
URL with a couple well no we can't we
can't add the same URL let's just use
some garbage URLs just to test
all
right okay that created properly now
let's test our new
endpoint this one is going to be a get
request to feeds and we don't need to
add any authentication information okay
run
that awesome this looks good to me we've
got an array at the top level and then
two feed objects one for garbage blog
and one's for one for Lane's blog so it
looks like everything's working so we've
given users a way to create feeds and a
way to query all of the feeds now we're
going to give users a way to follow
specific feeds so that they can see kind
of an aggregated view of all of the
feeds that they care about on the system
okay so uh let's go ahead and add a new
migration we need a new table so this
will be the fourth migration and we'll
call this new table feed follows and
this table is just going to store the
relationship between a user and all of
the feeds they're following so it'll be
a many to many uh kind of table of user
IDs to feed
IDs all right um the table is going to
be called feed
follows so create table feed follows
every feed follow like every other
record in our database will have an ID a
created at and an updated at but its
unique Fields will be a little bit
different first it's going to need a
user
ID which is
is oh my gosh why can't I type fingers
the wrong place in the keyboard so a
user ID is a uu ID um that can be it
doesn't let's see it doesn't need to be
unique um but it does need to be not
null then we need a feed
ID also a uu ID not null and then we're
going to create a unique constraint on
the comb ination of user ID to feed ID
so
unique user ID feed ID so again this
constraint is going to make it so that
we can never have two instances of a
follow for the same user feed
relationship right you as a user can
only follow a certain feed once you
can't follow it twice that doesn't
really make sense right so we're going
to ensure that that's unique um also so
I missed a couple things here the user
ID uh should reference so
[Music]
references uh the users
table ID field and on delete we'll
Cascade so if a user is deleted we're
going to go delete all of the data about
what feeds they're
following um and then this one's going
to be very similar except it references
the feeds table
with its ID and again if a feed gets
deleted then we'll go delete all of the
following data related to that
feed cool okay let's go ahead and run
this migration so I'm going to go back
up into the SQL or I should say back
down into the SQL schema
directory and from
here I'll need my connection string do
goose
postgress connection string
ah I didn't grab the whole thing let's
try that
again all of that boo
postgress
up cool feed follows databases or feed
follows table is there so now we need a
way for users to follow feeds all right
let's go ahead and go create that so I'm
going to copy and paste the feed Handler
file and we'll call it
Handler feed
follows and update this so Handler
create feed follow so remember in order
for a user to follow a feed all we need
to do is create a new feed follow record
with that user feed
relationship okay this is an
authenticated endpoint right we so we we
need a user and we need them to be
authenticated have passed an API key
right and let's see what do we need them
to give us as input I think all we need
is a feed
ID right they just need to tell us which
feed they want
to
follow so a feed ID is a uu
ID all right and now we should be able
to create feed oh we we never we never
made we never made the squl query what
am I doing what am I doing I'm getting
way ahead of myself let's go add that
query quickly so
feed follows
and to start we'll need a create feed
follow okay what's in a feed follow
right got all these all of these fields
and I think yeah we're just going to
have them all past in directly that
seems like the easiest way so insert
into feed
follows ID created at updated
at user ID feed ID
that's five parameters right one two
three four five cool that looks
good now I should be able to go back and
run sqlc
generate cool now I should
have
a create feed follow function with
create feed follow
pams all right it accepts a user ID and
a feed ID so the user ID is just the
auth authenticated user the feed ID is
going to be passed in his params
right cool couldn't create feed
follow don't need a get Handler quite
yet and then we're going to just need to
make uh make that uh mapping function as
well for feed
follows so in our models file I'll
create a new feed follow struct and it's
going to have a user ID and a feed
ID and a new function database feed
follow to feed follow
all right DB feed
fall. by the way I'm not using GitHub
co-pilot in this video just so that you
could just just so you can see more of
my thought process um but I typically do
use GitHub co-pilot
and it makes this kind of function just
like way faster to write um it would
guess this kind of function almost
perfectly um so just so you know I I do
recommend those kinds of tools to speed
up the development process um I'm just
not using it uh right now so that you
can see how I think through you know
architecting this this application
without all the AI prompts getting in
the way okay now we should be able to
database feed follow to feed follow and
we're going to be clear that this is a
feed follow not a
feed and that goes
there
Perfect all right let's hook this up so
we're going to need to go into main.go
V1 router. poost is we're creating a
resource slash feed
follows and this is an
authenticated authenticated
Handler Handler create feed follow okay
let's test this new endpoint so we'll
build and run the
server and we'll need a new
request this one will be kind of similar
it'll
be oops a post request to the feed
follows end
point and we're going to need to
authenticate so let's go grab some
authentication
information let's see get users let's go
ahead
and send this couldn't get user sequel
no result okay I need to figure out what
users I have available to me oh that's
right we we changed this API key we
wanted it to break let's go create a new
user we'll make a new one
called uh Billy
and there's Billy's API
key cool we've got some
feeds but our feed
follows need an off
section sorry in the
headers we're doing it
manually
authorization API key there's Billy's
key and then in the body we need to pass
in the ID of the feed that we want to
follow so so let's do a get on all of
the feeds and we can follow either of
these let's follow Lane's
block there's our feed
ID paste that in there and
create amazing new ID for the feed
follow there's the user ID the feed ID
what happens if we try to recreate it
cool couldn't create feed follow
duplicate key value violates unique
constraint that's what we'd expect right
we shouldn't be able to follow the same
feed multiple times we're already
following it we already have a record uh
indicating that we are following it
everything appears to be working just
fine next let's give users a way to see
all of the different feeds that they are
currently following so we'll do get feed
follows and it will return
many and the query will be select star
from
e follows where user ID equals dollar
sign one right so get all the feed
follows for a given
ID let's get that hooked up need to run
SQL C generate to create that query and
then down here we'll create a new
Handler this Handler will also be
authenticated but it's going to be get
feed
follows have the user we don't need any
parameters
here and we're just going to call get
feed follows and we'll just need to pass
in the user's
ID couldn't get feed
follows cool now we've got a list of
feed follows or a slice of feed follows
so we're going to need to convert an
entire slice so again here we'll write
this type of a function it's going to be
database
feed
follows to feed
follows all right
lots of copying and pasting here feed
follows okay so now we have a way to
convert an entire slice of database feed
follows to our own
struct that looks good to
me there feed
[Music]
follows okay cool now we have a Handler
for getting feed follows let's go ahead
and update this
so we need a new V1 router.
getet SL feed
follows middle wear
off get feed
follows Perfect all right let's give
that a
shot so we'll build and run again
and now let's see so this is um this is
the request that we used to
create so let's
grab oh so hardworking on such a small
screen let's grab our API key and create
a new
request 80 slv1 feed follows it's going
to be a get request it does need to be
authenticated okay see if that
works cool we got the one feedback that
we are currently following finally we
need a way to unfollow feeds or to
delete feed follows so let's create a
new one new
query we'll do delete feed follow now
this one is going to be our first quy
that doesn't actually return
anything um it's just going to be an
execute right we're not returning one
record we're not returning many records
we're returning no records we're just
going to run a a SQL query so uh it'll
it's going to be
delete
from bead
follows where ID equals dollar sign one
and user ID equals sign too now it's
important it's important to point out
that we don't actually need the user ID
here for this query to work right the ID
is already a unique identifier the
reason I'm tacking on this user ID is
because this will prevent someone who
doesn't own a feed follow from trying to
unfollow a feed on behalf of somebody
else that makes sense uh if for whatever
reason another user
got access let's say if if for some
reason user B got access to the feed
follow ID of user
a if we didn't have this check here then
that user who hijacked a feed follow ID
would be able to like
unfollow like force the other user to do
an unfollow if that makes sense this
ensures that only the user who actually
owns the follow record can execute the
unfollow Comm command hope hopefully
that makes sense okay uh from here let's
just go ahead and generate
that and go hook it up to a new
endpoint so we'll do
Handler delete feed
follow now this one's going to be a
little
different in that it it is authenticated
but we need to get a feed follow ID and
delete requests so like HTTP delete
requests the delete HTTP method they
don't typically have a body in the
payload it's it's possible but I would
argue it's not super conventional um
it's a little more conventional to pass
the ID in the HTTP path so um it's going
to look something like
this view on router. delete feed follows
slash
feed follow
ID and then this will be
Handler uh Delete feed follow right so
we want the feed follow ID dynamically
passed in the path of the request so the
question is how do we grab this feed
follow ID um in our Handler itself well
the chai router has or Chi oh I'm I'm
never going to say that the proper way
uh the chi router has a I think it's p
is it URL let's see
URL parameter that's the one uh Ur URL
parameter function where we can pass in
the request and a key and in this case
it's going to have to match so feed
follow ID matches whatever we type in
here between the open and close
brackets okay and that's going to return
a string so this is the feed follow ID
string great we're going to take that
and we're going to par it into a uuid so
we'll do U id.
parse and that will return a feed follow
ID and potentially an
error if the error does not equal nil
we'll say couldn't parse feed follow
ID and that will be a 400 level errror
perfect okay from here we should be able
to do apic CFG do database. delete feed
follow and we need to pass in the
request
context
and feed follow params so database. feed
delete feed follow parameters it takes
an ID and a user ID so the ID of the
feed
follow we just parsed and then the user
ID comes in with that user object
because this is an authenticated
request cool and that should return just
an
error right oh we it's just it's just
giv me yellow squigglies because I need
to handle the
air
couldn't elete
speed
follow perfect uh what do we respond
with here I guess we have a couple
different options um the simplest thing
would just be to respond with like an
empty Json object I guess
uh what matters to the client is
probably the 200 Response Code um so we
could like for the sake of Simplicity
just so we can use our respond with Json
function we'll just return an empty Json
object alternatively maybe we could
return an object that says like message
you know unfollow successful or
something um but it it doesn't matter
too much I think as long as it's a 200
level code uh we're pretty much good to
go
okay and that's already been hooked
up so let's go ahead and test
it uh I can't remember if I generated
let me do that again and then we'll
restart the
server and take a
look okay so this was our endpoint it's
returning the feeds that we're currently
following let's go ahead and delete this
feed follow so we need new request
this is going to be a delete request
we're going to unfollow a specific ID
we're going to unfollow this we're going
to delete this feed follow right feed
follow with that with that
ID and the headers we do need to be
authenticated as the same
person so
authorization same API key okay let's
run that delete we got a 200 response
now let's go do a get and make sure that
it's gone yep empty list or empty array
we're good to go okay we've built out
the majority of the crud section of our
API but we haven't built the most
interesting part which is the part of
the server that actually goes out and
fetches posts from the different RSS
feeds that exist in our database again
the whole purpose of this server that
we're building is so that it can keep
track of all of these different feeds in
the database and then go out
periodically and actually download all
of the posts that are on each individual
feed so for example we have a feed for
my personal blog post this server will
actually go out to my blog every I don't
know 10 minutes and check to see if
there's a new blog post to download and
store in the database so the first thing
we need to do is update the feeds table
to have one more column we need a new
column called last fetched at and it's
just so we can keep track of when we
last fetched the posts for a given feed
so let's go ahead and add that we'll
need new migration um and it will look
kind of like this migration uh it's
going to be our fifth migration so far
it's going to be on the feeds table and
we're going to be adding the last
fetched at last fetched at field okay so
alter table feeds add column last
fetched
at and this one is going to
be
timestamp and it will be nullable so we
don't need a notnull constraint
um in fact that's it
um it's okay like we don't need to
specify any defaults uh that should be
it um and then as far as the down
migration goes we'll just be deleting or
dropping the column from the feeds table
okay cool let's run that
migration so Goose
postgress perfect so we don't need to
update the create feed uh
function we want the last fetch St field
to default to null so no changes are
necessary there but we do need a new we
do need a new query this one's going to
be
called get next oh my gosh get next feed
to fetch can't type today get next feed
to fetch and it will return a single
row and this one should say select
star from
feeds order
by last fetched at descending
NS
first limit
one okay so we always this the purpose
of this function is to go get the feed
that next needs to be fetched like we
need to go get posts for this feed next
and the whole idea is first we want to
go find any feeds that have never been
fetched before those need to take
priority after that if every feed has
been fetched then we want to go find the
one that was fetched the longest ago
like the farthest in the past right so
we're ordering by last fetched at um
nulls first in descending
order actually scratch that we're going
to want to do a ascending right
ascending would put the lowest the
smallest time stamps right the ones
further in the past at the top and then
Ascend into the present okay so order by
last fetch thatat ascend ending NS first
perfect okay just to make sure that my
SQL code is valid we'll generate that
looks good okay next we need one more
query this one will be
called uh Mark feed
fetched Mark feed I guess as
fetched this is the one we'll call after
we fetch a feed to say that we fetched
it um and we'll return the updated feed
okay so it's going to be
update feeds
set last fetched at equal to
now and updated at also equal to now so
we haven't really gone over this but the
updated at and created at fields are
mostly for auditing purposes it's pretty
standard practice to set these fields on
basically every record in an SQL
database just so you can see when
they've been created and updated it's
kind of again auditing
purposes okay uh where
ID equals dollar sign
one
and returning
star okay so we update the feeds we set
the last fetch at and the updated at to
the current time for the given ID that
looks good to me let's go ahead and
generate that perfect next we need a way
to kind of take an RSS URL or a feed URL
and parse it into an an actual response
body and in this case we're going to
represent it as a struct let me show you
what I mean so let's create a new file
I'm just going to call it rs.
Go and it's going to be part of the main
package and we need a new function and
we're going to call it RSS
to or actually let's call it
url url to
feed
okay and it's going to take as input a
URL which is just a string and it will
return a new type so we need to specify
the new type type um RSS
feed it will return both an RSS feed and
potentially an error if there's
something wrong uh with the request that
it's making now that RSS feed struct
that we just created is going to
represent basically this giant this
giant document here right so if you go
to Wags lane. deev index.xml which is a
valid RSS feed then you'll see this
giant document and really you can think
of RSS as just structured data in XML
format and XML is just kind of like
crappy Json so the way we parse XML in
go is very similar to The Way We parse
Json let me show you what I mean I've
done the Dirty Work of scanning all of
the valid um values in that big RSS
document and I found that basically
these are the
keys um for the RSS entries in my blog
so RSS is kind of a standardized set of
keys within XML um and basically what
I'm saying is these are the keys that we
care about right at the top level of an
RSS feed we expect a channel key right
in the XML document and we expect a
channel to have a title a link a
description a language and then a slice
of items and then items are kind of
these nested objects that each have
their own title link descriptions and
publication dates right and each item is
a new blog post and if you're asking how
I came up with those names of all of the
different Keys it's because I went and
looked here in this document I saw okay
at the top level we have a channel right
and then we have um this entry with a
title a link a description right so I
just kind of manually looked through
this document and found all the stuff
that I wanted to parse out so let's fill
in the rest of this URL to feed function
so first we're going to need an HTTP
client um I'm just creating a new client
using the HTTP Library um we'll set it
to a timeout of 10 seconds if it takes
more than 10 seconds to fetch an RSS
feed uh we don't want that feed anyway
probably broken okay uh then we can use
that client to make a get request
to the URL of the feed and that's going
to return an HTTP response and
potentially an
error if there's an error we'll just
return let's just
do um for cons for ease of use I'm going
to make this a pointer to an RSS feed so
we can just return nil and the
airor cool um if everything's okay then
we're going to defer a
close on let's close on the resp sorry
it's not it's not the close function
it's resp. body
close Okay and then after that we want
to get all of the data from the response
body so it's going to be io. readall we
have to read everything from resp. body
and that comes back as a slice of bites
and an
error okay this slice of bytes we want
to read
into this RSS feed so dealing with XML
in go is very similar to dealing with
Json and go it's actually going to be
XML
dot
unmarshall pass in the data and a
pointer to where we want to unmarshal
the data so actually I need to create an
empty struct we need RSS
feed is an empty RSS feed
struct then we'll unmarshall into that
location in
memory that will return an
error if everything goes well then we
can just return the new populated RSS
feed perfect now as I type this out I'm
already kind of dissatisfied with this
pointer solution I don't think that
needs to be a pointer I think we should
just return empty
structs um either way would work I think
this is a little cleaner though because
it means the user of this function us
right uh will will get an actual RSS
feedback and not a pointer to an RSS
feed okay let's go ahead and test this
really quick I'm just going to do a
little kind of hacky
thing just right at the top of main I'm
going to call URL to feed and give it
the URL of um my blog so waglan
dodev index.xml
that should return a feed and an
error right and then if error not equal
nil log.
fatal error otherwise I want to just
print out fmt do print line let's just
print out the whole feed it'll be
disgusting but at least we'll get to see
if it kind of worked okay let's build
and run that
there we
go cool so if you kind of scroll through
this you'll see it looks like I mean
there was no errors and then it looks
like we
properly at least you know at first
glance looks like we properly filled out
that struct it's kind of just dumping
all of the data so now that we've done a
sanity test on our URL to feed function
let's go write the actual ual
scraper create a new file just call this
scraper.com so we can really see so we
can actually see what uh what we're
dealing with here so it'll take three
inputs a connection to the database um a
number of
concurrency units I guess the best way
to think about this is how many
different go routines we want to do the
scraping on and then how much time uh we
want in between each request to go
scrape a new RSS feed cool and it
shouldn't return anything because this
is going to be a long running job now
because this worker this scrap rper is
going to be running in the background of
our server I think it's really important
that we have good logging um that kind
of tells us what's going on as it's
happening so when we start scraping I'm
going to do a a little log message here
so log.
printf we say
scraping on percent V go
routines every percent s duration
pass in the concurrency and the time
between
requests cool after that we need to
figure out like how we're going to make
our requests on this interval and
there's a really cool um mechanism in
the standard library and go called a
ticker so we can create a new ticker uh
using the standard Library so time. new
ticker and we give it a duration in this
case time between
requests and it responds with a
ticker and then we can use a for
Loop to execute the body of the for Loop
every time a new value comes across the
ticker's channel so the ticker Has a
Field called C which is a channel where
every kind of let's say that you know
time between request was set to one
minute in that case every one minute a
value would be sent across the channel
so by using this syntax here we could
say run this for Loop every one minute
and the reason I'm passing in an empty
initializes and an empty um middle
section to the for Loop is so that it
executes immediately the first time so
the very first time we get to line 17
the body of the for Loop will requ will
will fire immediately and then it will
wait for the for the uh interval on the
ticker if that makes sense if we just
did this oops if we just did what is it
for
range tier. C uh then it would actually
wait for the minute up front but I want
to do it once immediately um it'll make
it easier to debug and work with now at
this point I realized that I've made a
mistake the purpose of this concurrency
parameter here is to uh you know
indicate to the start scraping function
how many go routines we want to use to
go fetch um all these different feeds
and the whole point is that we can fetch
them at the same time time so that means
that each time that this ticker fires we
need to be potentially go you know going
out to the internet to fetch 10 20 30
different RSS feeds and download all of
their blog posts at the same time which
means we'll actually need to be able to
grab a multiple number of feeds we'll
need to grab more than just one at a
time so rather than get next feed to
fetch let's change this to get next
feeds to fetch and we'll have it return
many
and then rather than limiting to one
let's limit to dollar sign one so we can
actually pass in how many feeds we want
as a parameter to this function okay
then we should be able to regenerate
that and we should oh we're not even
using the function yet so okay so that
was actually the perfect time to do that
okay let's fill out the body of this for
Loop so every interval time between
request we want to go grab the next
batch of feeds to fetch so we can just
call that function that we just wrote
database. getet next feeds to fetch it
takes a context and a limit so the first
thing we'll just use context. background
so again I haven't gone into a ton of
detail on the context package but
basically context. background is like
the global context it's what you use if
you don't have access to a scoped
context like we do for our individual
TTP requests okay so that'll work for
now and then we also need to pass in a
limit so we'll just cast int 32 and the
limit or the sorry the
concurrency and that should return some
feeds and an
err if there's an error we should
probably print something
now notice I'm continuing here that's
because this function should always be
running as our
server operates like there's no time in
which we want this function to ever stop
so if I returned here that would be a
problem it would actually stop scraping
completely just because maybe I don't
know our database connection was down
temporarily so for now we're just going
to log and continue now that we have a
slice of feeds let's write some logic
that goes and fetches each feed
individually and importantly fetches
each individually at the same time so
we're going to need a synchronization
mechanism uh I'm going to use a weight
group so the standard library has this
awesome thing called a sync. weight
group then we can iterate over all of
the feeds so four feed range feeds
okay so the way that the weight group
works is anytime you want to spawn a new
go routine within the context of the
weight group you do a weight group. add
and you add some number to it so here
I'm iterating over all of the feeds that
we want to fetch on individual go
routines and I'm going to add one to the
weight
group then at the end of the loop I can
do a weight group.
weight and and within the loop I can
spawn a new go routine so we're going to
go do some function in fact I guess I
should just name it kind of what we'll
be doing uh let's call it scrape feed go
scrape feed and here we're actually
going to pass the weight group in as one
of the
parameters and
within scrape
bead takes a weight group which is a
pointer to a
sync. weight
group within this function we'll defer a
weight group.
done okay so what happens here
basically we're iterating over the the
the all of the feeds on the same go
routine as the you know the start
scraping uh function so on the main go
routine on the main go routine we are
adding one to the weight group for every
feed right so say we had a concurrency
of 30 we would be adding 30 to the
weight group now we'll be spawning all
of these separate go routines as we do
that and when we get to the end of the
loop we're going to be waiting on the
weight group for 30 30
distinct calls to done so done
effectively decrements the counter by
one right done decrements the counter by
one so we're adding one every time we
iterate over the slice and then we're
calling done when we're done actually
scraping the feed so what this does is
it allows us to call scrape feed at the
same time 30 times we go spawn 30
different go routines to scrape 30
different RSS
feeds and when they're all done line 35
will kind of execute and we'll move past
that until they're like before they're
done we'll be blocking on line 35 which
is what we want to do because we don't
want to continue on to the next
iteration of the loop until we are sure
that we've actually scraped all of the
feeds so we've sort of stubbed out this
scrape feed function right now it
doesn't do anything other than call
weight group. let's actually go scrape
some feeds so it's going to need access
to a database
connection and it's also going to need a
specific feed to go fetch so feed is a
datab base. feed
cool and then here we can pass in
database and
feed great the first thing scrape feed
should do and and keep in mind we're
deferring the weight group. dun so this
will always be called at the end of this
function um the first thing we should do
is Mark that we've fetched this feed or
that we're fetching this feed so it's
going to be database. Mark feed as
fetched we can just use the background
context
again and we need the ID of the feed so
feed dot
ID cool that should return an error if
there was an error I think oh it also
Returns the updated feed I don't think
we care about the updated feed so I
think we can ignore
that say if air is not equal nil now
keep in mind we are not returning
anything from this function remember
we're calling it on a new go routine so
there's nothing to return here if
there's an error instead we'll just log
there was an issue
and return nothing and next we need to
actually do the heavy lifting which is
to go out and scrape the feed so we
already wrote Our URL to feed function
let's just use that so URL to feed and
we'll pass in the feed. URL and we
should get back an RSS feed and an
air and if error does not equal
nil error fetching
feed and we'll return
there otherwise we need to do
some logging so in the future what we'll
do is instead of iterating over all of
the um items in the RSS struct that we
get get back and just printing them to
the console we'll be saving them into
the database but for now just so that we
can test our scrape feed function um
we're just going to log log all of this
to the console so we're going to log um
each individual post or rather that we
found a post um and then how many posts
we found the last thing we need to do is
go hook up this start scraping function
to our main function so that it actually
starts okay so start scraping takes
database concurrency actually I'll just
open this uh my screen's too small to be
working in two tabs at the same time
okay um we're going to need to call it
before listen and serve because remember
this is where our server kind of blocks
and waits forever for incoming requests
so we should probably call it I don't
know
right here seems like a good
spot so um it's just it's just a
function right yeah it's not a method
takes database concurrency and time
between request okay so we go go start
scraping remember we want to call it on
a new go routine so it doesn't interrupt
uh this main flow because remember start
scraping is never going to return it's a
long running function uh this is an
infinite for Loop okay it needs a
database connection so we'll actually
need to save this
in a new variable so that we can use it
in the API config and in the start
scraping
function next we need the currency um
let's just start with 10 seems good and
then time between requests let's do
time.
minute perfect I went ahead and added a
second RSS feed so let's go ahead and
check the database and see what feeds we
have currently so I've got Lane's blog
and the boot Dev blog so there's two now
remember we were setting a concurrency
of 10 so we should definitely be able to
fetch both of these blogs at the same
time on the first iteration of that Loop
if we say deployed this production and
allowed users to start creating feeds
maybe we'd get up to 100 200 400
different feeds in here then we'd only
be fetching 10 at a time right just so
we understand how that mechanism works
but for now this should be good enough
to test I'm going to update our logs
just a bit um so that we can see which
blog each post is from so found post
item.
tile on
feed feed. name okay let's go ahead and
run that
bound post the property sub pointers in
go on feed boot Dev blog perfect we kind
of scroll up we should be able to see
some the boot Dev Blog has way more blog
posts than my personal blog here it is
here's some Lane's blog stuff okay so
that looks like it's working we should
be able to move on to the next step now
where we'll actually save these blog
posts into the database rather than just
logging the titles to the console we're
going to need a new table in our
database so let's start there we'll call
it
posts so the purpose of this table is to
store all of the posts that we are
fetching from all of the different uh
from all of the different RSS feeds okay
um what am I doing this is this is
queries we need to start with a
migration so let's grab this we'll make
it 006
posts.
SQL all right Goose up is going to be
create table
posts what goes in a posts table um we
are going to need an ID created at an
updated at that pretty much never
changes um what else does a post have
well it has a
title
title um is that text. null that makes
sense uh posts also typically have a
description text um I'm going to allow
that one to be nullable I think it's
okay if a post is missing its
description posts also typically have a
published at date so published at is a
timestamp um should we allow that one to
be
nullable no no let's make that not
null what else does a post have it has a
URL a URL should be not null for
sure um I'm also going to say that it
should be
unique
unique I don't think we ever want to
store the same Post in the database
twice there's no point right if we have
a post I don't see why we would need it
a second time so let's let's go ahead
and make that one unique and then lastly
let's just put in a feed
ID and the feed ID is going to be a uu
ID that
uh we're gonna want it to be a reference
so
references um feeds
ID right and that's going to
be does that need to be unique it
doesn't need to be unique but it should
be not null we should always have the
feed ID of a post and let's here put on
delete Cascade if we delete a feed we'll
Cascade and delete all of its posts and
I forgot to put the type here so URL
text. nil unique okay that looks good to
me let's go ahead and run our migration
so it's going to be uh we need to CD
into SQL
schema and run Goose
postgress we'll need this connection
string
up perfect next we'll need a couple of
queries to interact with this table uh
the first one's going to be just a way
to create a post so let's do posts
and we'll
do
create
oops create post return one thing and
we'll just kind of be inserting a bunch
of stuff I think let's take a look at
the post table so we've got ID created
at update at let me just grab this so I
don't forget
it okay so insert into
posts ID created updated
at we also have we have a lot of stuff
so I'm actually going to start spacing
this out a little
bit cre at update at it's going to be a
title and a
description publish
that
URL and a feed ID
okay and the values 1 2 3 4 how many do
we got just
eight
five 6
7 eight returning
star straightforward right insert into
posts all of these fields no fancy logic
that should be good okay let's run sqlc
generate to add that function to our
internal package and then we just need
to go use it so down in the scraper now
instead of just logging all of these
posts to the console let's save them to
the database um I'm going to leave this
log message it says feed blank collected
blank posts found so that'll just log
all of the different feeds we're
collecting but each individual post I
think it's wasteful and kind of busy to
log everything so we're not going to do
that instead we'll just call db. create
post context.
background and what does it take create
post prams database. create post prams
okay cool I kind of like how
sqlc breaks down the parameters into um
into a struct makes it pretty simple to
work with Okay um we've got an ID
created at updated at okay um ID is just
going to be a new ID
created at updated at that'll just be
time.
now
UTC what's next title description
tile item.
tile I just realized I just realized now
that I'm not putting the field names
it's kind of
embarrassing okay title
description do they not have a
description what does an item
have let's take a look at an item items
have title link description yeah it does
have a description what am I messing up
here can I use item. description
variable of type string as SQL n string
ah right okay
so we need to do SQL do null string a
null string has the string itself and
whether or not it's valid so we just uh
put in the string and then we say it is
valid string do valid is a bolean
true although actually this is a problem
right this is a problem because if item.
description is blank if it's an empty
string we're going to be putting in an
empty string and saying that it's there
even though it's not so let's not do
this let's do something a little
different let's
go
description is a new sq.
nring why is why is that giving me
trouble oh because because I'm doing it
within the call to create post do it
right here okay so we'll create a new
SQL n string and then we'll
say if item. description does not equal
the empty string then we get to set
description. string equal to item.
description and description.
valid equals
true and then we'll use the description
here does that make sense so if if the
item's description is blank then we'll
set the value to null in the database
effectively um otherwise we'll create
the valid uh description entity
okay next we need a publish
dat let's see we've got an item. pubdate
which is a string okay so we're going to
need to parse that string to parse that
date there is a time. parse function in
the standard library and we're going to
use this RFC 111
23z layout so this is the layout that
I'm using on the boot Dev blog and on my
blog to be more robust and support all
of the different publishing format for
all the different blogs that we want to
scrip we'd probably need to make this
logic a little bit more robust but for
now I'm just going to say we're parsing
it this way if it's not that way I guess
we take a hike okay um if there's an
error so if error does not equal
nil we can say log. print
line parse date
sent V with air sent V and we'll pass in
the actual Pub
date and an error and that's going to be
a print
F oops cool and then if that's an issue
we'll just continue so if we don't get a
valid time uh then we'll just we'll just
log it log and move
on okay so published at pass in that I
shouldn't use single name variables like
this let's do Hub
at okay what else do we got a URL and a
feed ID so
URL is just going to be the item.
link and feed
ID do we have access to the feed ID
here we do we have the feed right
because we passed in the feed here
perfect
b.id now db. cre post does return an
error so we need to handle that
error what am I screwing up here oh it
also Returns the post itself I don't
think we care about the new post though
I think all we care about is if it
failed so if
error equal
nil fa to create post with
error oh okay let's give that a shot
okay build and
run now remember we're expecting this
time to get logs that just say that the
blogs were collected so 21 posts from
Lane's blog 321 posts from the boot Dev
blog were collected I'm going to go
ahead and kill the server and let's
check PG admin so now if we select star
from
Posts we should see a bunch of stuff in
here IDs created at titles descriptions
published at dates awesome this is
looking
fantastic we scroll down to see how many
rows there are 342 that looks right to
me now I think we have an issue here let
me show you what I mean if we run this
again so remember we've scraped both of
the feeds and pulled in all of the posts
so if I rerun my server at this
point yeah we're getting all of these
issues fail to create post duplicate key
value violates unique constraint posts
URL key right now this makes sense we
didn't want to store duplicate posts in
our database so we have a unique
constraint on the post URL which means
when we go try to recreate the posts it
fails because we already have the posts
in our database so let's do a little
string uh a string detection so that we
don't log this crap every time this
happens because this isn't really an
error this is expected Behavior so um we
can do something like
if string do
contains air.
error uh duplicate
[Music]
duplicate
e then do we continue what are we doing
here yeah then we
continue otherwise we'll log the eror so
we're only going to log the error if
it's not a duplicate key error okay so
let's run that again and make sure we
don't get those
errors perfect we have one last feature
to add to our RSS aggregator we need a
way for users to be able to get a list
of all of the newest posts from the
feeds that they're following so we'll
need a new
query uh we can call this
one get posts for user and it will
return many posts now let's think about
this query for a second it's a little
more complex than the other queries
we've done and that I think we need to
do a join so we have our posts
table right post have IDs created at
updated at but importantly they have a
feed ID so we know what feed every Post
in the database belongs to and we also
have a feed follows table that tells us
which feeds an individual user is
following so if we join those two tables
together right if we take all of the
feed follow information kind of join it
to the posts table then we should be
able to filter by all of the feeds that
a user is actually following so what
does that look
like you can do select posts. star so
everything from the post
table from
Posts
join feed
follows
on oops on post do feed ID equals feed
follows. feed ID okay so this adds
essentially all the feed follow
information to our like the virtual
table for this query right we're joining
those two tables together so now we
should be able to filter um the way we
want Okay so we've we've joined them
together uh
where posts dot wait posts no where feed
follows. user
ID
equals dollar sign one okay so we join
the tables and then we filter all the
posts down or rather the entire table
down by the specific user ID so all of
the posts that belong to feeds that the
user is not following should at this
step get trimmed
out then we can order by let's do post.
published at descending so it give us
the newest stuff first
and we'll limmit by a configurable
amount so dollar sign
two cool let's go ahead and try to
generate
that looks like it worked let's see if
uh see if when we run the actual
application it does what we expect let's
hook that query up to a new endpoint so
I'm here in the users file that seems
like a reasonable place we'll do
Handler get posts for user
it will be an authenticated endpoint so
we'll need that that user data um but
here we're going to call DB dot or sorry
API
config DB doget posts for user and then
we can pass in the request. context and
user. ID oh we also need I think a
limit oh get no no no sorry we take we
take database. get post for user params
right because we had multiple parameters
here so we'll need the user's ID and a
limit the user. ID and for now let's
just say a limit of 10 and that will
return to us some posts and potentially
an
error if we get an error we'll respond
with
it couldn't get
posts otherwise we need to return the
post themselves now we should go create
a special posts model right so that we
get our our own tag so type post
struct go mostly copy this
from our
internal post
model wherever it ended up here it
is
okay add some Json tags
goodness typing is
hard now here's an interesting
thing this SQL null string um is not
something that we're going to want to
use um in this struct because this is a
struct that Marshalls to Json the null
string object is a nested struct so if
we Marshal it directly to Json we would
actually get description as a Json key
and then string as a Json key and then
valid as a Json key so be a little
nested object there that's pretty bad
user experience because Json natively
supports kind of null in the sense that
you can just omit the the key um or use
like the actual value null um so what
we're going to do is instead do a
pointer to a
string and the way Json marshalling and
go works is if you have a pointer to a
string and it is nil then it will
Marshal to what you'd expect in Json
land which is that null
value okay then publish
that URL
and feed
ID okay did I miss anything let me look
over this really
quick that's looking
good and next we need the conversion so
we'll
do database post to post
now this one gets a little hairy right
um we probably need to do some logic
here so we'll say bar description
pointer to a
string and then if DB
post. description.
valid then we'll set description equal
to the address of DB post. description.
string cool then we can just directly
use the description variable
there all right published that
what else we got URL on Feed
ID okay and last but not least we need a
way to do it uh do the conversion for an
entire slice bunk DB or database posts
to posts
and the logic will look pretty much
identical to that but I actually think
it'll be easier to type it out so we'll
do uh posts slice post
it database post
post the database
post
okay there we
go lot of conversion logic there but now
we should be good to just respond with
some
Json and we can pass
in those posts as database posts cool
what am I get in
here truck literally uses unkeyed Fields
oh yeah let's not do
that we want an
ID and I think it's a
limit right what am I messing up user
ID
perfect okay let's build and run the
server
again oh I realize I made a mistake I
need to actually hook this up to
something so let's go back into main.go
we need a new
endpoint we'll do
get um I don't know user
feed now feed's probably a loaded term
in this uh in this application we should
say uh let's just do
posts and it's going to require
middleware so middleware off and API CFG
Dot
Handler get posts for user okay so get
SL posts it's an authenticated endpoint
Perfect all right let's rebuild and run
that
okay opening up thunder
client first we need to check well
actually let's just grab some off
information so
um this clearly has some
moth let's grab that API key create a
new
request HTTP col Local
Host V1
posts headers close
that
authorization API key okay so if I make
that request I'm getting back no posts
now we know we have posts in the
database but my my user that I'm
currently logged in as is not following
anything right I'm getting back the
empty array when I when I check my feed
follows um but I can check which feeds
Exist by running this API
request so let's grab this feed let's
grab the Wags Lane feed and let's go
follow that one so we'll post to feed
follows here this feed ID
okay so now I should be following let's
check my feed
follows great I'm following them so now
if I go get my
posts there we go I should just be
getting
posts from the Wags lane. Dead
Block perfect let's try following let's
try following um
the other one
so where is it feed follows no
feeds let's try following the boot Dev
blog as
well so post to feed
follows send that check my feed follows
now I'm following both now if I go get
my
posts perfect we're seeing stuff from
the boot Dev blog that's it thank than
you for sticking with me through all of
this mess we've created an amazing blog
aggregator that will actually work
pretty darn well at scale you could run
this thing uh you know over a long
period of time collect millions of blog
posts and it would do pretty well I hope
you had a ton of fun with this project I
do want to remind you that this is a
server right we've kind of been running
it stopping it restarting it but at the
end of the day you can just turn it on
ADD new feeds and follows and interact
with it directly and it will once a
minute go out and collect all of those
blog posts so you could just keep this
running on a Raspberry Pi in your house
um to aggregate you know blog posts
podcasts all that kind of stuff um I
will point out that we have done a bit
of happy path programming so happy path
programming is when you're not
necessarily handling every Edge case out
there you're you're handling kind of you
the thing that you expect to happen most
of the time so for example um we only
had one type of date parsing for the
published at dates in our RSS feeds but
maybe there are RSS feeds out there that
use a different date format and we'll
fail to parse them um so one way that
you could extend this project would be
to just add a ton of new RSS feeds and
make sure that you deal with the issues
as they come up make sure you improve
the logging so that you can see the
issues when they come up anyways I hope
you had a ton of fun with this project
and that you learned something I just
want to remind you that we do have an
entire backend learning path over on
boot. in Goan so if you liked this
project if you liked this course and are
looking for some more content definitely
go check out boot. we also have
published a lot of different ways that
you could potentially extend this
project to make it cooler for example
maybe you add a front end or a command
line application that interacts directly
with the API so that you don't need to
use manual client like thunder client
every time that you want to interact
with your posts and then I also just
want to remind you before I go that you
can find me on Twitter at Wags lane or
on YouTube at boot. definitely go
subscribe to our YouTube channel as well
thank you again to free code camp for
allowing us to publish this course and
this project walk through I hope you
enjoyed it and I'll see you in the next
one
Can't find what you're looking for?
Get subtitles in any language from opensubtitles.com, and translate them here.