The Golang Handbook – A Beginner's Guide to Learning Go
The Go programming language has been exploding in popularity. Tons of companies are using Go to build scalable, modern, backend infrastructure. If you're looking to learn a new programming language, Go is a greatchoice. It's fast, lightweight, has an amazing open source community, and is actually quite easy to get started with. This a completely free text-based handbook. If you want to get started, just scroll down and start reading! That said, there are two other options for following along. Try the interactive version of this Golang course on Boot.dev, complete with coding challenges and projects Watch the video walkthrough of this course on FreeCodeCamp's YouTube channel (embedded below) Why Learn Go? How to Compile Go Code Variables in Go Functions in Go Structs in Go Interfaces in Go Errors in Go Loops in Go Arrays and Slices in Go Maps in Go Advanced Functions in Go Pointers in Go Local Development in Go Channels in Go Mutexes in Go Generics in Go Go is fast, simple, and productive.Go is one of the fastest programming languages, beating JavaScript, Python, and Ruby handily in most benchmarks. But, Go code doesn't runquite as fast as its compiled Rust and C counterparts. That said, it compilesmuch faster than they do, which makes the developer experience super productive. Unfortunately, there are no swordfights on Go teams... Comic byxkcd Go has been growing like crazy in the backend development industry, so if you're interested in getting a job as a backend dev, Go can be a great choice of technology to add to your tool belt. I typically recommend one of two ways: Official Download Webi Downloader Make sure to use at least version We'll go over this all later in more detail, but to sate your curiosity for now, here are a few tidbits about the code: Save the code above in a file called You can also use Bootdev's Go playground to try out all the snippets in this course right from your browser. Computers need machine code – they don't understand English or even Go. We need to convert our high-level (Go) code into machine language, which is really just a set of instructions that some specific hardware can understand. In your case, your CPU. The Go compiler's job is to take Go code and produce machine code. On Windows, that would be a Computers don't know how to do anything unless we as programmers tell them what to do. Unfortunately, computers don't understand human language. In fact, they don't even understand uncompiled computer programs. For example, the code: means nothingto a computer. A computer's CPU only understands its own instruction set, which we call "machine code". Instructions are basic math operations like addition, subtraction, multiplication, and the ability to save data temporarily. For example, an ARM processor uses the ADDinstruction when supplied with the number Go, C, and Rust are all languages where the code is first converted to machine code by the compiler before it's executed. Compiled programs can be run without access to the original source code, and without access to a compiler. For example, when your browser executes the code you write in this course, it doesn't use the original code, just the compiled result. Note how this is different than interpreted languages like Python and JavaScript. With Python and JavaScript, the code is interpreted at runtime by a separate program known as the "interpreter". Distributing code for users to run can be a pain because they need to have an interpreter installed, and they need access to the original source code. Go C C++ Rust JavaSsript Python Ruby Illustration of compiled vs interpreted languages Go enforces strong and static typing, meaning variables can only have a single type. A One of the biggest benefits of strong typing is that errors can be caught at "compile time". In other words, bugs are more easily caught ahead of time because they are detected when the code is compiled before it even runs. Contrast this with most interpreted languages, where the variable types are dynamic. Dynamic typing can lead to subtle bugs that are hard to detect. With interpreted languages, the code mustbe run (sometimes in production if you are unlucky 😨) to catch syntax and type errors. As an example, the following code will fail to compile because strings and ints can't be added together: Go programs are fairly lightweight. Each program includes a small amount of "extra" code that's included in the executable binary. This extra code is called the Go Runtime. One of the purposes of the Go runtime is to cleanup unused memory at runtime. In other words, the Go compiler includes a small amount of extra logic in every Go program to make it easier for developers to write code that's memory efficient. As a general rule, Java programs use morememory than comparable Go programs because Go doesn't use an entire virtual machine to run its programs, just a small runtime. The Go runtime is small enough that it is included directly in each Go program's compiled machine code. As another general rule, Rust and C++ programs use slightly lessmemory than Go programs because more control is given to the developer to optimize memory usage of the program. The Go runtime just handles it for us automatically. Chart showing idle memory usage comparison between Java (162MB), Go (.86MB) and Rust (.36MB) In the chart above, Dexter Darwich compares the memory usage of three verysimple programs written in Java, Go, and Rust. As you can see, Go and Rust use verylittle memory when compared to Java. Go's basic variable types are: We talked about A Don't worry too much about the intricacies of the other types for now. We will cover some of them in more detail as we progress. Variables are declared using the To declare a variable called The value of an initialized variable with no assignment will be its zero value. Inside a function (even the main function), the Is the same as: Outside of a function (in the global/package scope), every statement begins with a keyword ( To declare a variable without specifying an explicit type (either by using the When the right hand side of the declaration is typed, the new variable is of that same type: However, when the right hand side is a literal value (an untyped numeric constant like We can declare multiple variables on the same line: Ints, uints, floats, and complex numbers all have type sizes. The size (8, 16, 32, 64, 128, and so on) indicates how many bits in memory will be used to store the variable. The default The standard sizes that should be used unless you have a specific need are: Some types can be converted the following way: Casting a float to an integer in this way truncates the floating point portion. With so many types for what is essentially just a number, developers coming from languages that only have one kind of A problem arises when we have a This style of development can be slow and annoying to read. When Go developers stray from the “default” type for any given type family, the code can get messy quickly. Unless you have a good reason to, stick to the following types: Constants are declared like variables but use the Constants can be character, string, boolean, or numeric values. They can notbe more complex types like slices, maps and structs, which are types I will explain later. As the name implies, the value of a constant can't be changed after it has been declared. Constants mustbe known at compile time. More often than not they will be declared with a static value: However, constants can be computedso long as the computation can happen at compile time. For example, this is valid: That said, you can't declare a constant that can only be computed at run-time. Go follows the printf tradition from the C language. In my opinion, string formatting/interpolation in Go is currently lesselegant than JavaScript and Python. fmt.Printf – Prints a formatted string to standard out fmt.Sprintf() – Returns the formatted string These formatting verbs work with both The If you're interested in all the formatting options, feel free to take a look at the An This is just some syntactic sugar that Go offers to shorten up code in some cases. For example, instead of writing: We can do: Not only is this code a bit shorter, but it also removes Functions in Go can take zero or more arguments. To make Go code easier to read, the variable type comes afterthe variable name. For example, the following function: Accepts two integer parameters and returns another integer. Here, When multiple arguments are of the same type, the type only needs to be declared after the last one, assuming they are in order. For example: If they are not in order they need to be defined separately. Developers often wonder why the declaration syntax in Go is different from the tradition established in the C family of languages. The C language describes types with an expression including the name to be declared, and states what type that expression will have. The code above declares Interestingly, the creators of the Go language agreed that the C-style of declaring types in signatures gets confusing really fast – take a look at this nightmare. Go's declarations are clear, you just read them left to right, just like you would in English. It's nice for more complex signatures, as it makes them easier to read. Variables in Go are passed by value (except for a few data types we haven't covered yet). "Pass by value" means that when a variable is passed into a function, that function receives a copyof the variable. The function is unable to mutate the caller's original data. A function can return a value that the caller doesn't care about. We can explicitly ignore variables by using an underscore: For example: Even though There could be many reasons. For example, maybe a function called This is crucial to understand because the Go compiler will throw an error if you have unused variable declarations in your code, so you needto ignore anything you don't intend to use. Return values may be given names, and if they are, then they are treated the same as if they were new variables defined at the top of the function. Named return values are best thought of as a way to document the purpose of the returned values. According to the tour of go: "A return statement without arguments returns the named return values. This is known as a "naked" return. Naked return statements should be used only in short functions. They can harm readability in longer functions." Is the same as: In the first example, Even though a function has named return values, we can still explicitly return values if we want to. Using this explicit pattern we can even overwrite the return values: Otherwise, if we want to return the values defined in the function signature we can just use a naked Named return parameters are great for documenting a function. We know what the function is returning directly from its signature, no need for a comment. Named return parameters are particularly important in longer functions with many return values. Which is easier to understand than: We know the meaningof each return value just by looking at the function signature: If there are multiple return statements in a function, you don’t need to write all the return values each time, though you probably should. When you choose to omit return values, it's called a nakedreturn. Naked returns should only be used in short and simple functions. Go supports the ability to return early from a function. This is a powerful feature that can clean up code, especially when used as guard clauses. Guard Clauses leverage the ability to Error handling in Go naturally encourages developers to make use of guard clauses. When I started writing more JavaScript, I was disappointed to see how many nested conditionals existed in the code I was working on. Let’s take a look at an exaggerated example of nested conditional logic: This could be written with guard clauses instead: The example above is much easier to read and understand. When writing code, it’s important to try to reduce the cognitive load on the reader by reducing the number of entities they need to think about at any given time. In the first example, if the developer is trying to figure out With the one-dimensional structure offered by guard clauses, it’s as simple as stepping through each case in order. We use structs in Go to represent structured data. It's often convenient to group different types of variables together. For example, if we want to represent a car we could do the following: This creates a new struct type called In Go, you will often use a struct to represent information that you would have used a dictionary for in Python, or an object literal in JavaScript. Structs can be nested to represent more complex entities: The fields of a struct can be accessed using the dot An anonymous struct is just like a regular struct, but it is defined without a name and therefore cannot be referenced elsewhere in the code. To create an anonymous struct, just instantiate the instance immediately using a second pair of brackets after declaring the type: You can even nest anonymous structs as fields within other structs: In general, prefer named structs. Named structs make it easier to read and understand your code, and they have the nice side-effect of being reusable. I sometimes use anonymous structs when I know I won't ever need to use a struct again. For example, sometimes I'll use one to create the shape of some JSON data in HTTP handlers. If a struct is only meant to be used once, then it makes sense to declare it in such a way that developers down the road won’t be tempted to accidentally use it again. You can read more about anonymous structs here if you're curious. Go is not an object-oriented language. But embedded structs provide a kind of data-onlyinheritance that can be useful at times. Keep in mind, Go doesn't support classes or inheritance in the complete sense. Embedded structs are just a way to elevate and share fields between struct definitions. An embedded struct's fields are accessed at the top level, unlike nested structs. Promoted fields can be accessed like normal fields except that they can't be used in composite literals While Go is notobject-oriented, it does support methods that can be defined on structs. Methods are just functions that have a receiver. A receiver is a special parameter that syntactically goes beforethe name of the function. A receiver is just a special kind of function parameter. Receivers are important because they will, as you'll learn in the exercises to come, allow us to define interfaces that our structs (and other types) can implement. Interfaces are collections of method signatures. A type "implements" an interface if it has all of the methods of the given interface defined on it. In the following example, a "shape" must be able to return its area and perimeter. Both When a type implements an interface, it can then be used as the interface type. Interfaces are implemented implicitly. A type never declares that it implements a given interface. If an interface exists and a type has the proper methods defined, then the type automatically fulfills that interface. A type can implement any number of interfaces in Go. For example, the empty interface, Consider the following interface: Based on the code alone, can you deduce what kindsof strings you should pass into the We know the function signature expects 2 string types, but what are they? Filenames? URLs? Raw string data? For that matter, what the heck is that Let's add some named arguments and return data to make it clearer. Much better. We can see what the expectations are now. The first argument is the When working with interfaces in Go, every once-in-awhile you'll need access to the underlying type of an interface value. You can cast an interface to its underlying type using a type assertion. A type switchmakes it easy to do several type assertions in a series. A type switch is similar to a regular switch statement, but the cases specify typesinstead of values. Writing clean interfaces is hard. Frankly, anytime you’re dealing with abstractions in code, the simple can become complex very quickly if you’re not careful. Let’s go over some rules of thumb for keeping interfaces clean. If there is only one piece of advice that you take away from this article, make it this: keep interfaces small! Interfaces are meant to define the minimal behavior necessary to accurately represent an idea or concept. Here is an example from the standard HTTP package of a larger interface that’s a good example of defining minimal behavior: Any type that satisfies the interface’s behaviors can be considered by the HTTP package as a File. This is convenient because the HTTP package doesn’t need to know if it’s dealing with a file on disk, a network buffer, or a simple An interface should define what is necessary for other types to classify as a member of that interface. They shouldn’t be aware of any types that happen to satisfy the interface at design time. For example, let’s assume we are building an interface to describe the components necessary to define a car. Instead, the developer should have relied on the native functionality of type assertion to derive the underlying type when given an instance of the car interface. Or, if a sub-interface is needed, it can be defined as: Which inherits the required methods from Interfaces are not classes, they are slimmer. Interfaces don’t have constructors or deconstructors that require that data is created or destroyed. Interfaces aren’t hierarchical by nature, though there is syntactic sugar to create interfaces that happen to be supersets of other interfaces. Interfaces define function signatures, but not underlying behavior. Making an interface often won’t DRY up your code in regards to struct methods. For example, if five types satisfy the Go programs express errors with When something can go wrong in a function, that function should return an A Because errors are just interfaces, you can build your own custom types that implement the It can then be used as an error: Go programs express errors with Keep in mind that the way Go handles errors is fairly unique. Most languages treat errors as something special and different. For example, Python raises exception types and JavaScript throws and catches errors. In Go, an The Go standard library provides an "errors" package that makes it easy to deal with errors. Read the godoc for the errors.New() function, but here's a simple example: The basic loop in Go is written in standard C-like syntax: For example: Loops in Go can omit sections of a for loop. For example, the Most programming languages have a concept of a For example: Which prints: The The Arrays are fixed-size groups of variables of the same type. The type To declare an array of 10 integers: or to declare an initialized literal: 99 times out of 100you will use a slice instead of an array when working with ordered lists. Arrays are fixed in size. Once you make an array like A slice is a dynamically-sized, flexibleview of the elements of an array. Slices alwayshave an underlying array, though it isn't always specified explicitly. To explicitly create a slice on top of an array we can do: The syntax is: Where Either Most of the time we don't need to think about the underlying array of a slice. We can create a new slice using the Slices created with If we want to create a slice with a specific set of values, we can use a slice literal: Note that the array brackets do nothave a The length of a slice is simply the number of elements it contains. It is accessed using the built-in The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice. It is accessed using the built-in Generally speaking, unless you're hyper-optimizing the memory usage of your program, you don't need to worry about the capacity of a slice because it will automatically grow as needed. Many functions, especially those in the standard library, can take an arbitrary number of finalarguments. This is accomplished by using the "..." syntax in the function signature. A variadic function receives the variadic arguments as a slice. The familiar fmt.Println() and fmt.Sprintf() are variadic! The spread operator allows us to pass a slice intoa variadic function. The spread operator consists of three dots following the slice in the function call. The built-in append function is used to dynamically add elements to a slice: If the underlying array is not large enough, Notice that Go provides syntactic sugar to iterate easily over elements of a slice: For example: Maps are similar to JavaScript objects, Python dictionaries, and Ruby hashes. Maps are a data structure that provides key->value mapping. The zero value of a map is We can create a map by using a literal or by using the The If If Any type can be used as the valuein a map, but keysare more restrictive. You can read more in the following section of the official Go blog. As mentioned earlier, map keys may be of any type that is comparable. The language spec defines this precisely, but in short, comparable types are boolean, numeric, string, pointer, channel, and interface types, and structs or arrays that contain only those types. Notably absent from the list are slices, maps, and functions. These types cannot be compared using ==, and may not be used as map keys. It's obvious that strings, ints, and other basic types should be available as map keys, but perhaps unexpected are struct keys. Struct can be used to key data by multiple dimensions. For example, this map of maps could be used to tally web page hits by country: This is map of string to (map of string to int). Each key of the outer map is the path to a web page with its own inner map. Each inner map key is a two-letter country code. This expression retrieves the number of times an Australian has loaded the documentation page: Unfortunately, this approach becomes unwieldy when adding data, as for any given outer key you must check if the inner map exists, and create it if needed: On the other hand, a design that uses a single map with a struct key does away with all that complexity: When a Vietnamese person visits the home page, incrementing (and possibly creating) the appropriate counter is a one-liner: And it’s similarly straightforward to see how many Swiss people have read the spec: Maps can contain maps, creating a nested structure. For example: A programming language is said to have "first-class functions" when functions in that language are treated like any other variable. For example, in such a language, a function can be passed as an argument to other functions, can be returned by another function, and can be assigned as a value to a variable. A function that returns a function or accepts a function as input is called a Higher-Order Function. Go supports first-class and higher-order functions. Another way to think of this is that a function is just another type – just like For example, to accept a function as a parameter: Function currying is the practice of writing a function that takes a function (or functions) as input, and returns a new function. For example: In the example above, the The The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns. Deferred functions are typically used to close database connections, file handlers and the like. For example: In the above example, the Defer is a great way to make surethat something happens at the end of a function, even if there are multiple return statements. A closure is a function that references variables from outside its own function body. The function may access and assignto the referenced variables. In this example, the Anonymous functions are true to form in that they have no name. We've been using them throughout this chapter, but we haven't really talked about them yet. Anonymous functions are useful when defining a function that will only be used once or to create a quick closure. As we have learned, a variable is a named location in memory that stores a value. We can manipulate the value of a variable by assigning a new value to it or by performing operations on it. When we assign a value to a variable, we are storing that value in a specific location in memory. A pointer is a variable that stores the memory addressof another variable. This means that a pointer "points to" the locationof where the data is stored NOTthe actual data itself. The The Pointers allow us to manipulate data in memory directly, without making copies or duplicating data. This can make programs more efficient and allow us to do things that would be difficult or impossible without them. The A pointer's zero value is The The Unlike C, Go has no pointer arithmetic We're doing this exercise to understand that pointers canbe used in this way. That said, pointers can be verydangerous. It's generally a better idea to have your functions accept non-pointers and return new values rather than mutating pointer inputs. Again, pointers can be very dangerous. If a pointer points to nothing (the zero value of the pointer type) then dereferencing it will cause a runtime error (a panic) that crashes the program. Generally speaking, whenever you're dealing with pointers you should check if it's A receiver type on a method can be a pointer. Methods with pointer receivers can modify the value to which the receiver points. Since methods often need to modify their receiver, pointer receivers are more commonthan value receivers. Methods with pointer receivers don't require that a pointer is used to call the method. The pointer will automatically be derived from the value. Make sure you have Go installed on your local machine. Every Go program is made up of packages. You have probably noticed the A package named "main" has an entrypoint at the A package by any other name is a "library package". Libraries have no entry point. Libraries simply export functionality that can be used by other packages. For example: This program is an executable. It is a "main" package and importsfrom the By convention, a package's name is the same as the last element of its import path. For instance, the That said, package names aren't requiredto match their import path. For example, I could write a new package with the path While the above is possible, it is discouraged for the sake of consistency. A directory of Go code can have at mostone package. All Go programs are organized into packages. A package is a directory of Go code that's all compiled together. Functions, types, variables, and constants defined in one source file are visible to all other source files within the same package (directory). A repositorycontains one or more modules. A module is a collection of Go packages that are released together. A file named The module path The version of the Go language your project requires Optionally, any external package dependencies your project has The module path is just the import path prefix for all packages within the module. Here's an example of a Each module's path not only serves as an import path prefix for the packages within but also indicates where the go command should look to download it. For example, to download the module An "import path" is a string used to import a package. A package's import path is its module path joined with its subdirectory within the module. For example, the module You don't need to publish your code to a remote repository before you can build it. A module can be defined locally without belonging to a repository. But it's a good habit to keep a copy of all your projects on a remote server, like GitHub. Your machine will contain many version control repositories(managed by Git, for example). Each repository contains one or more packages, but will typically be a single module. Each package consists of one or more Go source filesin a single directory. The path to a package's directory determines its import pathand where it can be downloaded from if you decide to host it on a remote version control system like Github or Gitlab. The $GOPATH environment variable will be set by default somewhere on your machine (typically in the home directory, These days you should avoid working in the Navigate to a location on your machine where you want to store some code. For example, I store all my code in That said, you can put your code wherever you want. Once inside your personal workspace, create a new directory and enter it: Inside the directory declare your module's name: Where Print your Inside Conventionally, the file in the Paste the following code into your file: The I rarely use Execute Ensure you are in your hellogo repo, then run: Run the new program: Ensure you are in your Navigate out of your project directory: Go has installed the If you get an error regarding "hellogo not found" it means you probably don't have your Go environment setup properly. Specifically, You can read more about that here in the go install docs. Let's write a package to import and use in Create a sibling directory at the same level as the Initialize a module: Then create a new file Note that there is no Run: Let's learn how to use an open-source package that's available online. Be aware that using the "replace" keyword like we did in the last assignment isn't advised, but can be useful to get up and running quickly. The proper way to create and depend on modules is to publish them to a remote repository. When you do that, the "replace keyword can be dropped from the This works for local-only development This works if we publish our modules to a remote location like Github as we should. I’ve often seen, and have been responsible for, throwing code into packages without much thought. I’ve quickly drawn a line in the sand and started putting code into different folders (which in Go are different packages by definition) just for the sake of findability. Learning to properly build small and reusable packages can take your Go career to the next level. If you're familiar with the pillars of OOP, this is a practice in encapsulation. Oftentimes an application will have complex logic that requires a lot of code. In almost every case the logic that the application cares about can be exposed via an API, and most of the dirty work can be kept within a package. For example, imagine we are building an application that needs to classify images. We could build a package: We create an API by only exposing the function(s) that the application-level needs to know about. All other logic is unexported to keep a clean separation of concerns. The application doesn’t need to know how to classify an image, just the result of the classification. The unexported functions within a package can and should change often for testing, refactoring, and bug fixing. A well-designed library will have a stable API so that users aren’t receiving breaking changes each time they update the package version. In Go, this means not changing exported function’s signatures. A Perhaps one of the most important and most broken rules is that a package shouldn’t know anything about its dependents. In other words, a package should never have specific knowledge about a particular application that uses it. You can optionally read more here if you're interested. Concurrency is the ability to perform multiple tasks at the same time. Typically, our code is executed one line at a time, one after the other. This is called sequential executionor synchronous execution. If the computer we're running our code on has multiple cores, we can even execute multiple tasks at exactlythe same time. If we're running on a single core, a single code executes code at almostthe same time by switching between tasks very quickly. Either way, the code we write looks the same in Go and takes advantage of whatever resources are available. Go was designed to be concurrent, which is a trait fairlyunique to Go. It excels at performing many tasks simultaneously and safely using a simple syntax. There isn't a popular programming language in existence where spawning concurrent execution is quite as elegant, at least in my opinion. Concurrency is as simple as using the In the example above, Channels are a typed, thread-safe queue. Channels allow different goroutines to communicate with each other. Like maps and slices, channels must be created beforeuse. They also use the same The This reads and removes a value from the channel and saves it into the variable A deadlock is when a group of goroutines are all blocking so none of them can continue. This is a common bug that you need to watch out for in concurrent programming. Empty structs are often used as We can block and wait until somethingis sent on a channel using the following syntax This will block until it pops a single item off the channel, then continue, discarding the item. Channels can optionally be buffered. You can provide a buffer length as the second argument to Sending on a buffered channel only blocks when the buffer is full. Receiving blocks only when the buffer is empty. Channels can be explicitly closed by a sender: Similar to the ok is Sending on a closed channel will cause a panic. A panic on the main goroutine will cause the entire program to crash, and a panic in any other goroutine will cause that goroutineto crash. Closing isn't necessary. There's nothing wrong with leaving channels open, they'll still be garbage collected if they're unused. You should close channels to indicate explicitly to a receiver that nothing else is going to come across. Similar to slices and maps, channels can be ranged over. This example will receive values over the channel (blocking at each iteration if nothing new is there) and will exit only when the channel is closed. Sometimes we have a single goroutine listening to multiple channels and want to process data in the order it comes through each channel. A The first channel with a value ready to be received will fire and its body will execute. If multiple channels are ready at the same time one is chosen randomly. The The Mutexes allow us to lockaccess to data. This ensures that we can control which goroutines can access certain data at which time. Go's standard library provides a built-in implementation of a mutex with the sync.Mutex type and its two methods: .Lock() .Unlock() We can protect a block of code by surrounding it with a call to It's good practice to structure the protected code within a function so that Mutexes are powerful. Like most powerful things, they can also cause many bugs if used carelessly. Maps are notsafe for concurrent use! If you have multiple goroutines accessing the same map, and at least one of them is writing to the map, you must lock your maps with a mutex. Mutex is short for mutual exclusion, and the conventional name for the data structure that provides it is "mutex", often abbreviated to "mux". It's called "mutual exclusion" because a mutex excludesdifferent threads (or goroutines) from accessing the same data at the same time. The principle problem that mutexes help us avoid is the concurrent read/write problem. This problem arises when one thread is writing to a variable while another thread is reading from that same variable at the same time. When this happens, a Go program will panic because the reader could be reading bad data while it's being mutated in place. The example above creates a map, then starts two goroutines which each have access to the map. One goroutine continuously mutates the values stored in the map, while the other prints the values it finds in the map. If we run the program on a multi-core machine, we get the following output: In Go, it isn’t safe to read from and write to a map at the same time. In this example, we added a In the reader, we The standard library also exposes a sync.RWMutex In addition to these methods: Lock() Unlock() The RLock() RUnlock() The As we've mentioned, Go does notsupport classes. For a long time, that meant that Go code couldn't easily be reused in many circumstances. For example, imagine some code that splits a slice into 2 equal parts. The code that splits the slice doesn't really care about the valuesstored in the slice. Unfortunately in Go we would need to write it multiple times for each type, which is a very un-DRY thing to do. In Go 1.20 however, support for generics was released, effectively solving this problem! Put simply, generics allow us to use variables to refer to specific types. This is an amazing feature because it allows us to write abstract functions that drastically reduce code duplication. In the example above, You should care about generics because they mean you don’t have to write as much code! It can be frustrating to write the same logic over and over again, just because you have some underlying data types that are slightly different. Generics give Go developers an elegant way to write amazing utility packages. While you will see and use generics in application code, I think it will much more common to see generics used in libraries and packages. Libraries and packages contain importable code intended to be used in manyapplications, so it makes sense to write them in a more abstract way. Generics are often the way to do just that! Go places an emphasis on simplicity. In other words, Go has purposefully left out many features to provide its best feature: being simple and easy to work with. According to historical data from Go surveys, Go’s lack of generics has always been listed as one of the top three biggest issues with the language. At a certain point, the drawbacks associated with the lack of a feature like generics justify adding complexity to the language. Sometimes you need the logic in your generic function to know somethingabout the types it operates on. The example we used in the first exercise didn't need to know anythingabout the types in the slice, so we used the built-in Constraints are just interfaces that allow us to write generics that only operate within the constraint of a given interface type. In the example above, the Let's take a look at the example of a For example, a When generics were released, a new way of writing interfaces was also released at the same time! We can now simply list a bunch of types to get a new interface/constraint. Let's look at this simple example again: Remember, This is just as valid: If you're interested in doing the interactive coding assignments and quizzes for this course you can check out the Learn Go course over on Boot.dev. This course is a part of my full back-end developer career path, made up of other courses and projects if you're interested in checking those out. If you want to see the other content I'm creating related to web development, check out some of my links below: Lane's Podcast: Backend Banter Lane on Twitter Lane on YouTubeChapter 1 – Why Learn Go?

How to Download and Install the Go Toolchain
1.20. You can verify this after installation by typing:go versionA note on the structure of a Go program
package mainimport "fmt"func main() { fmt.Println("hello world")}package mainlets the Go compiler know that we want this code to compile and run as a standalone program, as opposed to being a library that's imported by other programs.import fmtimports the fmt(formatting) package. The formatting package exists in Go's standard library and let's us do things like print text to the console.func main()defines the mainfunction. mainis the name of the function that acts as the entry point for a Go program.main.go, run go build, and then run the resulting executable.go build -o out./outChapter 2 – How to Compile Go Code
What does it mean to be compiled?
.exefile. On Mac or Linux, it would be any executable file.package mainimport "fmt"func main(){ fmt.Println("hello world")}Computers need machine code
0100in binary.Go, C, Rust, and so on

Compiled vs Interpreted
Examples of compiled languages
Examples of interpreted languages

Go is Strongly Typed
stringvariable like "hello world" can not be changed to an int, such as the number 3.func main() { var username string = "wagslane" var password int = 20947382822 // don't edit below this line fmt.Println("Authorization: Basic", username+":"+password)}Go programs are lightweight

Chapter 3 – Variables in Go
boolstringint int8 int16 int32 int64uint uint8 uint16 uint32 uint64 uintptrbyte // alias for uint8rune // alias for int32 // represents a Unicode code pointfloat32 float64complex64 complex128strings and ints previously, and those two types should be fairly self-explanatory.boolis a boolean variable, meaning it has a value of trueor false. The floating point types ( float32and float64) are used for numbers that are not integers – that is, they have digits to the right of the decimal place, such as 3.14159. The float32type uses 32 bits of precision, while the float64type uses 64 bits to be able to more precisely store more digits.How to declare a variable
varkeyword. For example, to declare a variable called numberof type int, you would write:var number intpito be of type float64with a value of 3.14159, you would write:var pi float64 = 3.14159Short Variable Declaration
:=short assignment statement can be used in place of a vardeclaration. The :=operator infers the type of the new variable based on the value.var empty stringempty := ""numCars := 10 // inferred to be an integertemperature := 0.0 // temperature is inferred to be a floating point value because it has a decimal pointvar isFunny = true // isFunny is inferred to be a booleanvar, func, and so on) and so the :=construct is not available.Type Inference
:=syntax or var = expressionsyntax), the variable's type is inferredfrom the value on the right hand side.var i intj := i // j is also an int42or 3.14), the new variable will be an int, float64, or complex128depending on its precision:i := 42 // intf := 3.14 // float64g := 0.867 + 0.5i // complex128Same Line Declarations
mileage, company := 80276, "Tesla"// is the same asmileage := 80276company := "Tesla"Type Sizes
int int8 int16 int32 int64 // whole numbersuint uint8 uint16 uint32 uint64 uintptr // positive whole numbersfloat32 float64 // decimal numberscomplex64 complex128 // imaginary numbers (rare)intand uinttypes are just aliases that refer to their respective 32 or 64 bit sizes depending on the environment of the user.intuintfloat64complex128temperatureInt := 88temperatureFloat := float64(temperatureInt)Which Type Should I Use?
Numbertype (like JavaScript) may find the choices daunting.uint16, and the function we are trying to pass it into takes an int. We're forced to write code riddled with type casts like int(myUint16).boolstringintuintbyterunefloat64complex128Constants
constkeyword. Constants can't use the :=short declaration syntax.const myInt = 15const firstName = "Lane"const lastName = "Wagner"const fullName = firstName + " " + lastNameHow to Format Strings in Go
Examples
fmt.Printfand fmt.Sprintf.%v- Interpolate the default representation%vvariant prints the Go syntax representation of a value. You can usually use this if you're unsure what else to use. That said, it's better to use the type-specific variant if you can.s := fmt.Sprintf("I am %v years old", 10)// I am 10 years olds := fmt.Sprintf("I am %v years old", "way too many")// I am way too many years old%s- Interpolate a strings := fmt.Sprintf("I am %s years old", "way too many")// I am way too many years old%d- Interpolate an integer in decimal forms := fmt.Sprintf("I am %d years old", 10)// I am 10 years old%f- Interpolate a decimals := fmt.Sprintf("I am %f years old", 10.523)// I am 10.523000 years old// The ".2" rounds the number to 2 decimal placess := fmt.Sprintf("I am %.2f years old", 10.523)// I am 10.53 years oldfmtpackage's docs here.Conditionals
ifstatements in Go don't use parentheses around the condition:if height > 4 { fmt.Println("You are tall enough!")}else ifand elseare supported as you would expect:if height > 6 { fmt.Println("You are super tall!")} else if height > 4 { fmt.Println("You are tall enough!")} else { fmt.Println("You are not tall enough!")}The initial statement of an if block
ifconditional can have an "initial" statement. The variable(s) created in the initial statement are only defined within the scope of the ifbody.if INITIAL_STATEMENT; CONDITION { }length := getLength(email)if length < 1 { fmt.Println("Email is invalid")}if length := getLength(email); length < 1 { fmt.Println("Email is invalid")}lengthfrom the parent scope. This is convenient because we don't need it there – we only need access to it while checking a condition.Chapter 4 – Functions in Go
func sub(x int, y int) int { return x-y}func sub(x int, y int) intis known as the "function signature".Multiple Parameters
func add(x, y int) int { return x + y}Function Declaration Syntax
C-Style syntax
int y;yas an int. In general, the type goes on the left and the expression on the right.int (*fp)(int (*ff)(int x, int y), int b)Go-style syntax
x intp *inta [3]intf func(func(int,int) int, int) intHow to Pass Variables by Value
func main(){ x := 5 increment(x) fmt.Println(x) // still prints 5, // because the increment function // received a copy of x}func increment(x int){ x++}How to Ignore Return Values
_func getPoint() (x int, y int) { return 3, 4}// ignore y valuex, _ := getPoint()getPoint()returns two values, we can capture the first one and ignore the second.Why would you ignore a return value?
getCirclereturns the center point and the radius, but you really only need the radius for your calculation. In that case, you would ignore the center point variable.Named Return Values
func getCoords() (x, y int){ // x and y are initialized with zero values return // automatically returns x and y}func getCoords() (int, int){ var x int var y int return x, y}xand yare the return values. At the end of the function, we could simply write returnto return the values of those two variables, rather that writing return x,y.Explicit Returns
func getCoords() (x, y int){ return x, y // this is explicit}func getCoords() (x, y int){ return 5, 6 // this is explicit, x and y are NOT returned}return(blank return):func getCoords() (x, y int){ return // implicitly returns x and y}The Benefits of Named Returns
Good For Documentation (Understanding)
func calculator(a, b int) (mul, div int, err error) { if b == 0 { return 0, 0, errors.New("Can't divide by zero") } mul = a * b div = a / b return mul, div, nil}func calculator(a, b int) (int, int, error) { if b == 0 { return 0, 0, errors.New("Can't divide by zero") } mul := a * b div := a / b return mul, div, nil}func calculator(a, b int) (mul, div int, err error)Less Code (Sometimes)
Early Returns
returnearly from a function (or continuethrough a loop) to make nested conditionals one-dimensional. Instead of using if/else chains, we just return early from the function at the end of each conditional block.func divide(dividend, divisor int) (int, error) { if divisor == 0 { return 0, errors.New("Can't divide by zero") } return dividend/divisor, nil}func getInsuranceAmount(status insuranceStatus) int { amount := 0 if !status.hasInsurance(){ amount = 1 } else { if status.isTotaled(){ amount = 10000 } else { if status.isDented(){ amount = 160 if status.isBigDent(){ amount = 270 } } else { amount = 0 } } } return amount}func getInsuranceAmount(status insuranceStatus) int { if !status.hasInsurance(){ return 1 } if status.isTotaled(){ return 10000 } if !status.isDented(){ return 0 } if status.isBigDent(){ return 270 } return 160}when270 is returned, they need to think about each branch in the logic tree and try to remember which cases matter and which cases don’t.Chapter 5 – Structs in Go
type car struct { Make string Model string Height int Width int}car. All cars have a Make, Model, Heightand Width.Nested Structs in Go
type car struct { Make string Model string Height int Width int FrontWheel Wheel BackWheel Wheel}type Wheel struct { Radius int Material string}.operator.myCar := car{ }myCar.FrontWheel.Radius = 5Anonymous Structs
myCar := struct { Make string Model string} { Make: "tesla", Model: "model 3"}type car struct { Make string Model string Height int Width int // Wheel is a field containing an anonymous struct Wheel struct { Radius int Material string }}When should you use an anonymous struct?
Embedded Structs
type car struct { make string model string}type truck struct { // "car" is embedded, so the definition of a // "truck" now also additionally contains all // of the fields of the car struct car bedSize int}Embedded vs nested
lanesTruck := truck{ bedSize: 10, car: car{ make: "toyota", model: "camry", },}fmt.Println(lanesTruck.bedSize)// embedded fields promoted to the top-level// instead of lanesTruck.car.makefmt.Println(lanesTruck.make)fmt.Println(lanesTruck.model)Struct Methods
type rect struct { width int height int}// area has a receiver of (r rect)func (r rect) area() int { return r.width * r.height}r := rect{ width: 5, height: 10,}fmt.Println(r.area())// prints 50Chapter 6 – Interfaces in Go
rectand circlefulfill the interface.type shape interface { area() float64 perimeter() float64}type rect struct { width, height float64}func (r rect) area() float64 { return r.width * r.height}func (r rect) perimeter() float64 { return 2*r.width + 2*r.height}type circle struct { radius float64}func (c circle) area() float64 { return math.Pi * c.radius * c.radius}func (c circle) perimeter() float64 { return 2 * math.Pi * c.radius}Multiple Interfaces
interface{ }, is alwaysimplemented by every type because it has no requirements.Naming interface args
type Copier interface { Copy(string, string) int}Copyfunction?intthat's being returned?type Copier interface { Copy(sourceFile string, destinationFile string) (bytesCopied int)}sourceFile, the second argument is the destinationFile, and bytesCopied, an integer, is returned.Type Assertions in Go
type shape interface { area() float64}type circle struct { radius float64}// "c" is a new circle cast from "s"// which is an instance of a shape.// "ok" is a bool that is true if s was a circle// or false if s isn't a circlec, ok := s.(circle)if !ok { // s wasn't a circle log.Fatal("s is not a circle")}radius := c.radiusType Switches in Go
func printNumericValue(num interface{ }) { switch v := num.(type) { case int: fmt.Printf("%T\n", v) case string: fmt.Printf("%T\n", v) default: fmt.Printf("%T\n", v) }}func main() { printNumericValue(1) // prints "int" printNumericValue("1") // prints "string" printNumericValue(struct{ }{ }) // prints "struct { }"}fmt.Printf("%T\n", v)prints the typeof a variable.Clean Interfaces
1. Keep Interfaces Small
type File interface { io.Closer io.Reader io.Seeker Readdir(count int) ([]os.FileInfo, error) Stat() (os.FileInfo, error)}[]byte.2. Interfaces Should Have No Knowledge of Satisfying Types
type car interface { Color() string Speed() int IsFiretruck() bool}Color()and Speed()make perfect sense, they are methods confined to the scope of a car. IsFiretruck()is an anti-pattern. We are forcing all cars to declare whether or not they are firetrucks. In order for this pattern to make any amount of sense, we would need a whole list of possible subtypes. IsPickup(), IsSedan(), IsTank()… where does it end??type firetruck interface { car HoseLength() int}carand adds one additional required method to make the cara firetruck.3. Interfaces Are Not Classes
fmt.Stringerinterface, they all need their own version of the String()function.Chapter 7 – Errors in Go
errorvalues. An Error is any type that implements the simple built-in error interface:type error interface { Error() string}erroras its last return value. Any code that calls a function that can return an errorshould handle errors by testing whether the error is nil.// Atoi converts a stringified number to an intergeri, err := strconv.Atoi("42b")if err != nil { fmt.Println("couldn't convert:", err) // because "42b" isn't a valid integer, we print: // couldn't convert: strconv.Atoi: parsing "42b": invalid syntax // Note: // 'parsing "42b": invalid syntax' is returned by the .Error() method return}// if we get here, then// i was converted successfullynilerror denotes success. A non-nil error denotes failure.The Error Interface
errorinterface. Here's an example of a userErrorstruct that implements the errorinterface:type userError struct { name string}func (e userError) Error() string { return fmt.Sprintf("%v has a problem with their account", e.name)}func sendSMS(msg, userName string) error { if !canSendToUser(userName) { return userError{ name: userName} } ...}errorvalues. Error-values are any type that implements the simple built-in error interface.erroris just another value that we handle like any other value – however, we want! There aren't any special keywords for dealing with them.The errors Package
var err error = errors.New("something went wrong")Chapter 8 – Loops in Go
for INITIAL; CONDITION; AFTER{ // do something}INITIALis run once at the beginning of the loop and can create variables within the scope of the loop.CONDITIONis checked before each iteration. If the condition doesn't pass then the loop breaks.AFTERis run after each iteration.for i := 0; i < 10; i++ { fmt.Println(i)}// Prints 0 through 9How to Omit Conditions
CONDITION(middle part) can be omitted which causes the loop to run forever.for INITIAL; ; AFTER { // do something forever}No while loops in Go
whileloop. Because Go allows for the omission of sections of a forloop, a whileloop is just a forloop that only has a CONDITION.for CONDITION { // do some stuff while CONDITION is true}plantHeight := 1for plantHeight < 5 { fmt.Println("still growing! current height:", plantHeight) plantHeight++}fmt.Println("plant has grown to ", plantHeight, "inches")still growing! current height: 1still growing! current height: 2still growing! current height: 3still growing! current height: 4plant has grown to 5 inchesContinue through a loop
continuekeyword stops the current iteration of a loop and continues to the next iteration. continueis a powerful way to use the "guard clause" pattern within loops.for i := 0; i < 10; i++ { if i % 2 == 0 { continue } fmt.Println(i)}// 1// 3// 5// 7// 9Break out of a loop
breakkeyword stops the current iteration of a loop and exits the loop.for i := 0; i < 10; i++ { if i == 5 { break } fmt.Println(i)}// 0// 1// 2// 3// 4Chapter 9 – Arrays and Slices in Go
Arrays
[n]Tis an array of n values of type T.var myInts [10]intprimes := [6]int{ 2, 3, 5, 7, 11, 13}Slices
[10]intyou can't add an 11th element.primes := [6]int{ 2, 3, 5, 7, 11, 13}mySlice := primes[1:4]// mySlice = { 3, 5, 7}arrayname[lowIndex:highIndex]arrayname[lowIndex:]arrayname[:highIndex]arrayname[:]lowIndexis inclusive and highIndexis exclusivelowIndexor highIndexor both can be omitted to use the entire array on that side.How to Create New Slices in Go
makefunction:// func make([]T, len, cap) []TmySlice := make([]int, 5, 10)// the capacity argument is usually omitted and defaults to the lengthmySlice := make([]int, 5)makewill be filled with the zero value of the type.mySlice := []string{ "I", "love", "go"}3in them. If they did, you'd have an arrayinstead of a slice.Length
len()function:mySlice := []string{ "I", "love", "go"}fmt.Println(len(mySlice)) // 3Capacity
cap()function:mySlice := []string{ "I", "love", "go"}fmt.Println(cap(mySlice)) // 3Variadic Functions
func concat(strs ...string) string { final := "" // strs is just a slice of strings for str := range strs { final += str } return final}func main() { final := concat("Hello ", "there ", "friend!") fmt.Println(total) // Output: Hello there friend!}fmt.Println()prints each element with space delimiters and a newline at the end.func Println(a ...interface{ }) (n int, err error)Spread operator
func printStrings(strings ...string) { for i := 0; i < len(strings); i++ { fmt.Println(strings[i]) }}func main() { names := []string{ "bob", "sue", "alice"} printStrings(names...)}How to Append to a Slice
func append(slice []Type, elems ...Type) []Typeappend()will create a new underlying array and point the slice to it.append()is variadic. The following are all valid:slice = append(slice, oneThing)slice = append(slice, firstThing, secondThing)slice = append(slice, anotherSlice...)How to Range Over a Slice in Go
for INDEX, ELEMENT := range SLICE { }fruits := []string{ "apple", "banana", "grape"}for i, fruit := range fruits { fmt.Println(i, fruit)}// 0 apple// 1 banana// 2 grapeChapter 10 – Maps in Go
nil.make()function:ages := make(map[string]int)ages["John"] = 37ages["Mary"] = 24ages["Mary"] = 21 // overwrites 24ages = map[string]int{ "John": 37, "Mary": 21,}len()function works on a map – it returns the total number of key/value pairs.ages = map[string]int{ "John": 37, "Mary": 21,}fmt.Println(len(ages)) // 2Map Mutations
Insert an element
m[key] = elemGet an element
elem = m[key]Delete an element
delete(m, key)Check if a key exists
elem, ok := m[key]keyis in m, then okis true. If not, okis false.keyis not in the map, then elemis the zero value for the map's element type.Types of Valid Map Keys
hits := make(map[string]map[string]int)n := hits["/doc/"]["au"]func add(m map[string]map[string]int, path, country string) { mm, ok := m[path] if !ok { mm = make(map[string]int) m[path] = mm } mm[country]++}add(hits, "/doc/", "au")type Key struct { Path, Country string}hits := make(map[Key]int)hits[Key{ "/", "vn"}]++n := hits[Key{ "/ref/spec", "ch"}]Nested Maps
map[string]map[string]intmap[rune]map[string]intmap[int]map[string]map[string]intChapter 11 – Advanced Functions in Go
First-class and higher-order functions
ints and strings and bools.func add(x, y int) int { return x + y}func mul(x, y int) int { return x * y}// aggregate applies the given math function to the first 3 inputsfunc aggregate(a, b, c int, arithmetic func(int, int) int) int { return arithmetic(arithmetic(a, b), c)}func main(){ fmt.Println(aggregate(2,3,4, add)) // prints 9 fmt.Println(aggregate(2,3,4, mul)) // prints 24}Function Currying in Go
func main() { squareFunc := selfMath(multiply) doubleFunc := selfMath(add) fmt.Println(squareFunc(5)) // prints 25 fmt.Println(doubleFunc(5)) // prints 10}func multiply(x, y int) int { return x * y}func add(x, y int) int { return x + y}func selfMath(mathFunc func(int, int) int) func (int) int { return func(x int) int { return mathFunc(x, x) }}selfMathfunction takes in a function as its parameter, and returns a function that itself returns the value of running that input function on its parameter.Defer keyword
deferkeyword is a fairly unique feature of Go. It allows a function to be executed automatically just beforeits enclosing function returns.// CopyFile copies a file from srcName to dstName on the local filesystem.func CopyFile(dstName, srcName string) (written int64, err error) { // Open the source file src, err := os.Open(srcName) if err != nil { return } // Close the source file when the CopyFile function returns defer src.Close() // Create the destination file dst, err := os.Create(dstName) if err != nil { return } // Close the destination file when the CopyFile function returns defer dst.Close() return io.Copy(dst, src)}src.Close()function is not called until after the CopyFilefunction was called but immediately before the CopyFilefunction returns.Closures
concatter()function returns a function that has reference to an encloseddocvalue. Each successive call to harryPotterAggregatormutates that same docvariable.func concatter() func(string) string { doc := "" return func(word string) string { doc += word + " " return doc }}func main() { harryPotterAggregator := concatter() harryPotterAggregator("Mr.") harryPotterAggregator("and") harryPotterAggregator("Mrs.") harryPotterAggregator("Dursley") harryPotterAggregator("of") harryPotterAggregator("number") harryPotterAggregator("four,") harryPotterAggregator("Privet") fmt.Println(harryPotterAggregator("Drive")) // Mr. and Mrs. Dursley of number four, Privet Drive}Anonymous Functions
// doMath accepts a function that converts one int into another// and a slice of ints. It returns a slice of ints that have been// converted by the passed in function.func doMath(f func(int) int, nums []int) []int { var results []int for _, n := range nums { results = append(results, f(n)) } return results}func main() { nums := []int{ 1, 2, 3, 4, 5} // Here we define an anonymous function that doubles an int // and pass it to doMath allNumsDoubled := doMath(func(x int) int { return x + x }, nums) fmt.Println(allNumsDoubled) // prints: // [2 4 6 8 10]}Chapter 12 – Pointers in Go
x := 42// "x" is the name of a location in memory. That location is storing the integer value of 42A pointer is a variable
*syntax defines a pointer:var p *int&operator generates a pointer to its operand.myString := "hello"myStringPtr = &myStringWhy are pointers useful?
Pointer Syntax
*syntax defines a pointer:var p *intnil&operator generates a pointer to its operand:myString := "hello"myStringPtr = &myString*dereferences a pointer to gain access to the value:fmt.Println(*myStringPtr) // read myString through the pointer*myStringPtr = "world" // set myString through the pointerJust because you can doesn't mean you should
Nil Pointers
nilbefore trying to dereference it.Pointer Method Receivers
Pointer receiver
type car struct { color string}func (c *car) setColor(color string) { c.color = color}func main() { c := car{ color: "white", } c.setColor("blue") fmt.Println(c.color) // prints "blue"}Non-pointer receiver
type car struct { color string}func (c car) setColor(color string) { c.color = color}func main() { c := car{ color: "white", } c.setColor("blue") fmt.Println(c.color) // prints "white"}type circle struct { x int y int radius int}func (c *circle) grow(){ c.radius *= 2}func main(){ c := circle{ x: 1, y: 2, radius: 4, } // notice c is not a pointer in the calling function // but the method still gains access to a pointer to c c.grow() fmt.Println(c.radius) // prints 8}Chapter 13 – Local Development Environment in Go
Packages
package mainat the top of all the programs you have been writing.main()function. A mainpackage is compiled into an executable program.package mainimport ( "fmt" "math/rand")func main() { fmt.Println("My favorite number is", rand.Intn(10))}fmtand math/randlibrary packages.Package Names
Naming Convention
math/randpackage comprises files that begin with:package randgithub.com/mailio/randand name the package random:package randomOne Package / Directory
.gofiles in a single directory must all belong to the same package. If they don't an error will be thrown by the compiler. This is true for main and library packages alike.Go Modules
A Go repository typically contains only one module, located at the root of the repository.
go.modat the root of a project declares the module. It contains:go.modfile:module github.com/bootdotdev/exampleprojectgo 1.20require github.com/google/examplepackage v1.3.0golang.org/x/tools, the go command would consult the repository located at https://golang.org/x/tools.github.com/google/go-cmpcontains a package in the directory cmp/. That package's import path is github.com/google/go-cmp/cmp. Packages in the standard library do not have a module path prefix. – Paraphrased from Golang.org's code organizationDo I need to put my package on GitHub?
How to Set Up Your Machine
A note on GOPATH
~/go). Since we will be working in the new "Go modules" setup, you don't need to worry about that. If you read something online about setting up your GOPATH, that documentation is probably out of date.$GOPATH/srcdirectory. Again, that's the old way of doing things and can cause unexpected issues, so better to just avoid it.Get into your workspace
~/workspace, then organize it into subfolders based on the remote location. For example,~/workspace/github.com/wagslane/go-password-validator= https://github.com/wagslane/go-password-validatorHow to Write Your First Local Go Program
mkdir hellogocd hellogogo mod init { REMOTE}/{ USERNAME}/hellogo{ REMOTE}is your preferred remote source provider (i.e. github.com) and { USERNAME}is your Git username. If you don't use a remote provider yet, just use example.com/username/hellogogo.modfile:cat go.modThe Go Run Command
hellogo, create a new file called main.go.mainpackage that contains the main()function is called main.go.package mainimport "fmt"func main() { fmt.Println("hello world")}Run the code
go run main.gogo runcommand is used to quickly compile and run a Go package. The compiled binary is notsaved in your working directory. Use go buildinstead to compile production executables.go runother than to quickly do some testing or debugging.Further reading
go help runin your shell and read the instructions.The Go Build Command
go buildcompiles go code into an executable program.Build an executable
go build./hellogoGo Install
Build an executable
hellogorepo, then run:go installcd ../hellogoprogram globally. Run it with:hellogoTip about "not found"
go installis adding your binary to your GOBINdirectory, but that may not be in your PATH.How to Create a Custom Go Package
hellogo.hellogodirectory:mkdir mystringscd mystringsgo mod init { REMOTE}/{ USERNAME}/mystringsmystrings.goin that directory and paste the following code:// by convention, we name our package the same as the directorypackage mystrings// Reverse reverses a string left to right// Notice that we need to capitalize the first letter of the function// If we don't then we won't be able access this function outside of the// mystrings packagefunc Reverse(s string) string { result := "" for _, v := range s { result = string(v) + result } return result}main.goor func main()in this package.go buildwon't build an executable from a library package. However, go buildwill still compile the package and save it to our local build cache. It's useful for checking for compile errors.go buildHow to Publish Remote Packages in Go
A note on how you should publish modules
go.mod:Bad
module github.com/wagslane/hellogogo 1.20replace github.com/wagslane/mystrings v0.0.0 => ../mystringsrequire ( github.com/wagslane/mystrings v0.0.0)Good
module github.com/wagslane/hellogogo 1.20require ( github.com/wagslane/mystrings v0.0.0)Best Practices with Go Packages
1. Hide internal logic
package classifier// ClassifyImage classifies images as "hotdog" or "not hotdog"func ClassifyImage(image []byte) (imageType string) { return hasHotdogColors(image) && hasHotdogShape(image)}func hasHotdogShape(image []byte) bool { // internal logic that the application doesn't need to know about return true}func hasHotdogColors(image []byte) bool { // internal logic that the application doesn't need to know about return true}2. Don’t change APIs
3. Don’t export functions from the main package
mainpackage isn't a library, there's no need to export functions from it.4. Packages shouldn't know about dependents
Further Reading
Chapter 14 – Channels in Go
Concurrency

How does concurrency work in Go?
gokeyword when calling a function:go doSomething()doSomething()will be executed concurrently with the rest of the code in the function. The gokeyword is used to spawn a new goroutine.Channels in Go
Create a channel
makekeyword:ch := make(chan int)Send data to a channel
ch <- 69<-operator is called the channel operator. Data flows in the direction of the arrow. This operation will blockuntil another goroutine is ready to receive the value.Receive data from a channel
v := <-chv. This operation will blockuntil there is a value in the channel to be read.Blocking and deadlocks
Tokens
tokensin Go programs. In this context, a token is a unary value. In other words, we don't care whatis passed through the channel. We care whenand ifit is passed.<-chBuffered Channels
How to create a channel with a buffer
make()to create a buffered channel:ch := make(chan int, 100)How to Close Channels
ch := make(chan int)// do some stuff with the channelclose(ch)How to check if a channel is closed
okvalue when accessing data in a map, receivers can check the okvalue when receiving from a channel to test if a channel was closed.v, ok := <-chfalseif the channel is empty and closed.Don't send on a closed channel
Range Over a Channel
for item := range ch { // item is the next value received from the channel}Select from a channel
selectstatement is used to listen to multiple channels at the same time. It is similar to a switchstatement but for channels.select { case i, ok := <- chInts: fmt.Println(i) case s, ok := <- chStrings: fmt.Println(s)}okvariable in the example above refers to whether or not the channel has been closed by the sender yet.Select Default
defaultcase in a selectstatement executes immediatelyif no other channel has a value ready. A defaultcase stops the selectstatement from blocking.select { case v := <-ch: // use v default: // receiving from ch would block // so do something else}Chapter 15 – Mutexes in Go
Lockand Unlockas shown on the protected()method below.defercan be used to ensure that we never forget to unlock the mutex.func protected(){ mux.Lock() defer mux.Unlock() // the rest of the function is protected // any other calls to `mux.Lock()` will block}Maps are not thread-safe
Why is it called a Mutex?
Why use mutexes?

Mutex example
package mainimport ( "fmt")func main() { m := map[int]int{ } go writeLoop(m) go readLoop(m) // stop program from exiting, must be killed block := make(chan struct{ }) <-block}func writeLoop(m map[int]int) { for { for i := 0; i < 100; i++ { m[i] = i } }}func readLoop(m map[int]int) { for { for k, v := range m { fmt.Println(k, "-", v) } }}fatal error: concurrent map iteration and map writeMutexes to the rescue
package mainimport ( "fmt" "sync")func main() { m := map[int]int{ } mux := &sync.Mutex{ } go writeLoop(m, mux) go readLoop(m, mux) // stop program from exiting, must be killed block := make(chan struct{ }) <-block}func writeLoop(m map[int]int, mux *sync.Mutex) { for { for i := 0; i < 100; i++ { mux.Lock() m[i] = i mux.Unlock() } }}func readLoop(m map[int]int, mux *sync.Mutex) { for { mux.Lock() for k, v := range m { fmt.Println(k, "-", v) } mux.Unlock() }}sync.Mutex{ }and named it mux. In the write loop, the Lock()method is called before writing, and then the Unlock()is called when we're done. This Lock/Unlock sequence ensures that no other threads can Lock()the mutex while wehave it locked – any other threads attempting to Lock()will block and wait until we Unlock().Lock()before iterating over the map, and likewise Unlock()when we're done. Now the threads share the memory safely!RWMutex
sync.RWMutexalso has these methods:sync.RWMutexcan help with performance if we have a read-intensive process. Many goroutines can safely read from the map at the same time (multiple Rlock()calls can happen simultaneously). However, only one goroutine can hold a Lock()and all RLock()'s will also be excluded.Chapter 16 – Generics in Go
func splitIntSlice(s []int) ([]int, []int) { mid := len(s)/2 return s[:mid], s[mid:]}func splitStringSlice(s []string) ([]string, []string) { mid := len(s)/2 return s[:mid], s[mid:]}Type Parameters
func splitAnySlice[T any](s []T) ([]T, []T) { mid := len(s)/2 return s[:mid], s[mid:]}Tis the name of the type parameter for the splitAnySlicefunction, and we've said that it must match the anyconstraint, which means it can be anything. This makes sense because the body of the function doesn't careabout the types of things stored in the slice.firstInts, secondInts := splitAnySlice([]int{ 0, 1, 2, 3})fmt.Println(firstInts, secondInts)Why Generics?
Generics reduce repetitive code
Generics are used more often in libraries and packages
Why did it take so long to get generics?
Constraints in Go
anyconstraint:func splitAnySlice[T any](s []T) ([]T, []T) { mid := len(s)/2 return s[:mid], s[mid:]}anyconstraint is the same as the empty interface because it means the type in question can be anything.How to create a custom constraint
concatfunction. It takes a slice of values and concatenates the values into a string. This should work with any type that can represent itself as a string, even if it's not a string under the hood.userstruct can have a .String()that returns a string with the user's name and age.type stringer interface { String() string}func concat[T stringer](vals []T) string { result := "" for _, val := range vals { // this is where the .String() method // is used. That's why we need a more specific // constraint instead of the any constraint result += val.String() } return result}Interface type lists
// Ordered is a type constraint that matches any ordered type.// An ordered type is one that supports the <, <=, >, and >= operators.type Ordered interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string}How to Name Generic Types
func splitAnySlice[T any](s []T) ([]T, []T) { mid := len(s)/2 return s[:mid], s[mid:]}Tis just a variable name, We could have named the type parameter anything. Thappens to be a fairly common convention for a type variable, similar to how iis a convention for index variables in loops.func splitAnySlice[MyAnyType any](s []MyAnyType) ([]MyAnyType, []MyAnyType) { mid := len(s)/2 return s[:mid], s[mid:]}Congratulations on making it to the end!
- 最近发表
-
- Learn TypeScript – A Handbook for Developers
- AI in Agriculture: How AI
- Pioneering Next
- How to Build a PDF Page Numbering Tool in the Browser Using JavaScript
- Gamification Strategy Planning
- Beyond NVIDIA: Where the AI Infra Trade Actually Shows Up
- How to Use AI Effectively in Your Dev Projects
- How to Deploy an AI Agent with Amazon Bedrock AgentCore
- The Docker Handbook – Learn Docker for Beginners
- CSS Color Functions
- 随机阅读
-
- How to Run Private Text
- How to Create a GPU
- How to Build a Cost
- How to Use LangChain and LangGraph: A Beginner’s Guide to AI Workflows
- Bhavin Sheth
- Creating Memorable Web Experiences: A Modern CSS Toolkit
- How to Build an Automatic Knowledge Graph for Your Blog with PHP and JSON
- CSS Functions Guide
- The PHP Handbook – Learn PHP for Beginners
- rotateZ()
- How to Build an Autonomous OSINT Agent in Python Using Claude's Tool Use API
- Hall of Sponsors – freeCodeCamp
- Front End JavaScript Development Handbook – React, Angular, and Vue Compared
- How to Build a Live Options Database in Python – A Complete Guide
- A Developer’s Guide to Lazy Loading in React and Next.js
- Hall of Sponsors – freeCodeCamp
- Animation Tools for Web Developers
- How to Build Optimal AI Agents That Actually Work – A Handbook for Devs
- How to Build an Animated Shadcn Tab Component with Shadcn/ui
- freeCodeCamp.org Privacy Policy: Questions and Answers
- 搜索
-