Introduction
Welcome to this comprehensive Go programming course for beginners! In this course, you'll learn the key concepts and techniques to write performant, idiomatic Go code. Guided by experts Lane Wagner and Alan Lers, who have over 18 years combined coding experience, you'll master topics such as variables, functions, loops, and much more.
Course Highlights
- Hands-On Coding Lessons: Get hands-on experience with over 100 coding lessons and exercises.
- Real-World Projects: Apply your newfound skills by building seven real-world projects, including an RSS aggregator and implementing authentication with API keys.
- Scalable Infrastructure: Discover why Go has exploded in popularity among modern tech companies for building scalable backend infrastructure.
Why Choose Go?
Go's rapidly growing popularity makes it a crucial tool for any aspiring developer. Low complexity, great performance, and a fantastic developer experience make Go an excellent language to learn. Let's delve into why Go could be a game changer for your coding career:
- Fast Execution: Go is a compiled language, outperforming common interpreted languages.
- Compiling Efficiency: Go's fast compile times increase developer productivity significantly.
- Low Memory Usage: Processes run more efficiently utilizing less memory compared to languages like Java.
Interactive Learning Approach
Course Structure
The course starts with over 100 hands-on coding lessons. Towards the end, learners will build a production-ready backend server from scratch.
Practical Tools and Resources
- Free Resources: Utilize Discord chat groups, forums, and GitHub repositories provided in the course for additional resources and troubleshooting.
- Personalized Support: Connect with the instructors through social media platforms like Twitter or YouTube.
Getting Started with Go
Setting Up Your Environment
Before diving in, ensure you have the necessary development tools:
- Go Language: Install the Go programming language on your computer.
- Text Editor: Use an IDE or text editor like VS Code for building your applications.
- Command Line Terminal: Familiarize yourself with using command line tools to navigate and execute commands.
Introduction to Basic Syntax
Let's cover Go's foundational syntax and functionality:
- Variables: Understand how to declare and use variables, responding to data types efficiently.
- Control Structures: Explore loops, conditional statements, and functions.
- Packages and Modules: Learn about Go's package management system.
- Concurrent Programming: Understanding goroutines and how they boost Go's performance.
Advanced Programming Concepts
As you advance through the course, you'll explore more intricate aspects of Go programming:
- Error Handling: Learn best practices for managing errors in your Go applications.
- Concurrency: Understand the concurrency model with goroutines and channels.
- Structs and Interfaces: Use structs to define complex data types and interfaces to define behavior.
- Generics: Discover the power of generics in Go, introduced in version 1.20, helping you write less repetitive code.
Building Real-World Applications
Using the skills you've learned, you will create:
- An RSS aggregator to fetch and display content from various feeds.
- Implement user authentication and create both user and feed management capabilities.
Conclusion
This course equips you with everything you need to succeed in using Go programming effectively. Whether you aim to build innovative applications, enhance your career prospects, or simplify everyday software development tasks, Go will give you the tools to realize these goals. Dive in and start your journey with Go today!
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
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
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
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
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
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
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
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
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
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
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
runtime within every single binary that's built using the go programming language so we could think of it as
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
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
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
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
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
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
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
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
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
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
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
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
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
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
type a function that takes two ins and returns an INT and as its second parameter an INT and then F returns an
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
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
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
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
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
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
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
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
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
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
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
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
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 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
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
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
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
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
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
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
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
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
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
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
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
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
0.01 times that same thing the length of the body cool uh the print function should just print to standard out the
implements both of these interfaces and if we come down here to look at the test function this is interesting the test
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
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
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
feels at least like we followed the instructions let's see what we get not enough arguments in call to sms.
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 uh I think it was empty string just zero values right empty string and 0.0 okay cool let's see what
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
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
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
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
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
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
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
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
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
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
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
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
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
and then we need to pass in as parameters the values that we want to format or that we want to interpolate
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
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
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
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
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
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
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
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
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
one plus this like fractional part that grows over time so like the 10th message would have cost like
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
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
can send while keeping the cost under the threshold then basically I need to check and say if the total cost is
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
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
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
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
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
12 mod 4 we'll do in fact maybe I'll just write out all the problems first let's do
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
right if they're both multiples Fizz buzz if it's just three Fizz if it's just five Buzz okay let's try
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 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
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
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
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
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
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
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
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
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
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
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
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
strings okay let's just jump right into it if plan is plan Pro then we'll return all messages right
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
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
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
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
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
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
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
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
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
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
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
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
total cool pretty straightforward um let's go ahead and run that so summing three costs bill for the
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
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
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
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
costs by day actually it's going to be a four so like while the day is greater than the
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
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
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
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
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
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
that out first we need to create um or initialize the Matrix so let's say Matrix slice let's use make make slice
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
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
this inner loop is adding values to each individual row and then we just need to append the row to The Matrix so
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
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
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
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
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
words right and then here we can say if word equals equals bad word that means we found the bad word
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
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
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
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
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
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
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
cool so that should fill the entire user map with all of the names and phone numbers and then by the end we should
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
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
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
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
deletion we'll delete them and report back that we deleted them cool um otherwise if they're not scheduled for
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
inputs formats them given the formatter that we were given right and then just prints it to the console cool let's go
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
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
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
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 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
different adders two different instances of our Adder function right and one of them is going to count how many bills
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
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
2 right twice the length of the input message and then print cost report takes us second parameter which is the message
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
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
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
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
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
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
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
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
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
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
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 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
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
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
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
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
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
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
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 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
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
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
strings and here we need to initialize a new go module so I'll do go mod in it github.com Wags
properly again I'm going to just reopen VSS code to that level so we'll go back and find the
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
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
dependency we need to update our go dood file so edit the hell Go's go.mod file to contain the
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
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
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
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
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
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
CPU or Central Processing Unit works so your CPU has a clock speed um which basically like from a very high
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
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
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
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
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
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
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
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
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
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
function um and in here we we call Fibonacci let me just copy and paste that because I can't spell
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
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
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
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
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
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
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
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
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
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
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
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
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
nil right let's go ahead and run that okay charging customer for a yearly subscription current balance is 1,000
subscription new balance is less total number of line items is now three let's go take a look at those test cases just
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
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 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
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
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
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
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
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
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
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
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
went wrong maybe I'm misremembering how to use this uh use this package let's try just go. .load
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
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
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
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
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
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
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
Handler Readiness again this will be in the main package and we're going to create a new
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
new connection and an error and again if there's an error we'll just go ahead and log a
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
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
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
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
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
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
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
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
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
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
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
actually respond with the user object itself okay database. user and see how that goes well let's let's actually take
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
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
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
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
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
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
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
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
migrations should just undo whatever was done in the up migration let's go ahead and run this migration so I'm going to
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
here need to do that conversion so this just getting dropped because we weren't we weren't setting the API key
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
get and we're going to add some headers or specifically one header right we're going to add our authorization header
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
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
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
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
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
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
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
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
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
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
database so this is what our parameters should look like uh what error am I running into here cannot use API cf.
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
should never be null all right um with that we're actually going to need to go uh rerun our migrations so
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
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
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
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
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
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
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
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
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
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
so in main.go we'll create a new entry here this is going to be a get request and it's not
remember if I generated my SQL C so I'll do that couldn't hurt and then we'll build and run the
here update the body of my request sorry I'm so zoomed in so that you guys can see and it just
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
let's test our new endpoint this one is going to be a get request to feeds and we don't need to
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
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
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
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
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
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
follows and update this so Handler create feed follow so remember in order for a user to follow a feed all we need
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
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
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
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
there Perfect all right let's hook this up so we're going to need to go into main.go
authenticated authenticated Handler Handler create feed follow okay let's test this new endpoint so we'll
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
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
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
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
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
follows okay so now we have a way to convert an entire slice of database feed follows to our own
follows okay cool now we have a Handler for getting feed follows let's go ahead and update this
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
authenticated okay see if that works cool we got the one feedback that we are currently following finally we
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
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
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
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
ID comes in with that user object because this is an authenticated request cool and that should return just
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
okay and that's already been hooked up so let's go ahead and test it uh I can't remember if I generated
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
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
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
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
to fetch can't type today get next feed to fetch and it will return a single row and this one should say select
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
fetched Mark feed I guess as fetched this is the one we'll call after we fetch a feed to say that we fetched
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
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
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
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
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
the data so actually I need to create an empty struct we need RSS feed is an empty RSS feed
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
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
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
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
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
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
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
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
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
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
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
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
function next we need the currency um let's just start with 10 seems good and then time between requests let's do
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
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
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
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
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
the first one's going to be just a way to create a post so let's do posts and we'll
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
posts ID created updated at we also have we have a lot of stuff so I'm actually going to start spacing
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
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
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
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
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
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
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
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
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
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
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
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
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
post themselves now we should go create a special posts model right so that we get our our own tag so type post
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
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
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
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
in those posts as database posts cool what am I get in here truck literally uses unkeyed Fields
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
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
well so post to feed follows send that check my feed follows now I'm following both now if I go get
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
Heads up!
This summary and transcript were automatically generated using AI with the Free YouTube Transcript Summary Tool by LunaNotes.
Generate a summary for freeRelated Summaries
![Java Course Introduction: Mastering Coding Fundamentals and Data Structures](https://img.youtube.com/vi/yRpLlJmRo2w/default.jpg)
Java Course Introduction: Mastering Coding Fundamentals and Data Structures
Kickstart your Java programming journey with our guided course covering basics to algorithms for aspiring developers.
![Java Programming Course: Introduction, Structure, and Setup Guide](https://img.youtube.com/vi/yRpLlJmRo2w/default.jpg)
Java Programming Course: Introduction, Structure, and Setup Guide
Learn about Java programming fundamentals, data structures, and how to set up your coding environment.
![Docker for Beginners: A Comprehensive Guide to Containerization](https://img.youtube.com/vi/fqMOX6JJhGo/default.jpg)
Docker for Beginners: A Comprehensive Guide to Containerization
Learn Docker with hands-on labs, concepts, and advanced orchestration tools like Kubernetes.
![Java Programming: A Comprehensive Guide to Understanding Java and Its Concepts](https://img.youtube.com/vi/hBh_CC5y8-s/default.jpg)
Java Programming: A Comprehensive Guide to Understanding Java and Its Concepts
Explore Java programming concepts including OOP, exception handling, and collections. Learn how to build robust applications!
![Understanding Introduction to Deep Learning: Foundations, Techniques, and Applications](https://img.youtube.com/vi/QDX-1M5Nj7s/default.jpg)
Understanding Introduction to Deep Learning: Foundations, Techniques, and Applications
Explore the exciting world of deep learning, its techniques, applications, and foundations covered in MIT's course.
Most Viewed Summaries
![Pamamaraan ng Pagtamo ng Kasarinlan sa Timog Silangang Asya: Isang Pagsusuri](https://img.youtube.com/vi/rPneP-KQVAI/default.jpg)
Pamamaraan ng Pagtamo ng Kasarinlan sa Timog Silangang Asya: Isang Pagsusuri
Alamin ang mga pamamaraan ng mga bansa sa Timog Silangang Asya tungo sa kasarinlan at kung paano umusbong ang nasyonalismo sa rehiyon.
![Kolonyalismo at Imperyalismo: Ang Kasaysayan ng Pagsakop sa Pilipinas](https://img.youtube.com/vi/nEsJ-IRwA1Y/default.jpg)
Kolonyalismo at Imperyalismo: Ang Kasaysayan ng Pagsakop sa Pilipinas
Tuklasin ang kasaysayan ng kolonyalismo at imperyalismo sa Pilipinas sa pamamagitan ni Ferdinand Magellan.
![A Comprehensive Guide to Using Stable Diffusion Forge UI](https://img.youtube.com/vi/q5MgWzZdq9s/default.jpg)
A Comprehensive Guide to Using Stable Diffusion Forge UI
Explore the Stable Diffusion Forge UI, customizable settings, models, and more to enhance your image generation experience.
![Imperyalismong Kanluranin: Unang at Ikalawang Yugto ng Pananakop](https://img.youtube.com/vi/fJP_XisGkyw/default.jpg)
Imperyalismong Kanluranin: Unang at Ikalawang Yugto ng Pananakop
Tuklasin ang kasaysayan ng imperyalismong Kanluranin at mga yugto nito mula sa unang explorasyon hanggang sa mataas na imperyalismo.
![Pamaraan at Patakarang Kolonyal ng mga Espanyol sa Pilipinas](https://img.youtube.com/vi/QGxTAPfwYNg/default.jpg)
Pamaraan at Patakarang Kolonyal ng mga Espanyol sa Pilipinas
Tuklasin ang mga pamamaraan at patakarang kolonyal ng mga Espanyol sa Pilipinas at ang mga epekto nito sa mga Pilipino.