Skip to content

Instantly share code, notes, and snippets.

@prologic
Last active September 25, 2024 05:41
Show Gist options
  • Save prologic/5f6afe9c1b98016ca278f4d507e65510 to your computer and use it in GitHub Desktop.
Save prologic/5f6afe9c1b98016ca278f4d507e65510 to your computer and use it in GitHub Desktop.
Learn Go in ~5mins

Learn Go in ~5mins

This is inspired by A half-hour to learn Rust and Zig in 30 minutes.

Basics

Your first Go program as a classical "Hello World" is pretty simple:

First we create a workspace for our project:

$ mkdir hello
$ cd hello

Next we create and initialize a Go module:

$ go mod init hello

Then we write some code using our favorite editor in a file called main.go in the directory hello you created above:

package main

import "fmt"

func main() {
  fmt.Println("Hello World!")
}

And finally we build and produce a binary:

$ go build

You should now have a hello binary in your workspace, if you run it you should also get the output:

$ ./hello
Hello World!

Variables

You can create variables in Go in one of two ways:

var x int

Other types include int, int32, int64, float32, float64, bool and string (and a few others...), there are also unsigned variants of the integer types prefixed with u, e.g: uint8 which is the same as a byte.

Or implicitly with inferred types by creating and assigning a value with:

x := 42

Values are assigned by using the = operator:

x = 1

NOTE: Most of the time you don't need to use the var keyword unless you are creating a variable and assigning it a value later or want a zero or nil value for some reason (_like a nil map).

Functions

Functions are declared with the func keyword:

func hello(name string) string {
  return fmt.Sprintf("Hello %s", name)
}

Functions with a return type must explicitly return a value.

Functions can return more than one value (commonly used to return errors and values):

func isEven(n int) (bool, error) {
  if n <= 0 {
    return false, fmt.Errorf("error: n must be > 0")
  }
  return n % 2 == 0, nil
}

Go also supports functions as first-class citizens and as such supports many aspects of functional programming, including closures, returning functions and passing functions around as values. For example:

func AddN(n int) func(x int) int {
  return func(x int) int {
    return x + n
  }
}

Structs

As Go is a multi-paradigm language, it also support "object orientated" programming by way of "structs" (borrowed from C). Objects / Structs are defined with the struct keyword:

type Account struct {
  Id      int
  Balance float64
}

Fields are defined similar to variables and are accessed with the dot-operator .:

account := Account{}
fmt.Printf("Balance: $%0.2f", account.Balance)

Methods

Structs (objects) can also have methods. Unlike other languages however Go does not support multiple-inheritance nor does it have classes (you can however embed structs into other structs).

Methods are created like functions but take a "receiver" as the first argument:

type Account struct {
  id  int
  bal float64
}

func (a *Account) String() string {
  return fmt.Sprintf("Account[%d]: $0.2f", a.id, a.bal)
}

func (a *Account) Deposit(amt flaot64) float64 {
  a.bal += amt
  return a.bal
}

func (a *Account) Withdraw(amt float64) float64 {
  a.bal -= amt
  return a.bal
}

func (a *Account) Balance() float64 {
  return a.bal
}

These are called "pointer receiver" methods because the first argument is a pointer to a struct of type Account denoted by a *Account.

You can also define methods on a struct like this:

type Circle struct {
  Radius float64
}

func (c Circle) Area() float64 {
  return 3.14 * c.Radius * c.Radius
}

In this case methods cannot modify any part of the struct Circle, they can only read it's fields. They are effectively "immutable".

Arrays and Slices

Like other langauges Go has Arrays, but unlike other languages Go's arrays are more similar to C where they are of fixed size. You create a fixed sized array by specifying it's size and type like this:

xs := [4]int{1, 2, 3, 4}

Most of the time however you will deal with Slices, which behave more like lists in other languages like Python where they are resized automatically.

Sliaces are created by omitting the size:

xs := []int{1, 2, 3, 4}

Slices can also be created and appended to:

xs := []int{}
xs = append(xs, 1)
xs = append(xs, 2)
xs = append(xs, 3)

You can access an slice's elements by indexing:

xs[1]  // 2

You can also access a subset of an array or slice by "slicing" it:

ys := xs[1:] // [2, 3]

You can iterate over an array/slice by using the range keyword:

for i, x := range xs {
  fmt.Printf("xs[%d] = %d\n", i, x)
}

Maps

Go has a builtin data structure for storing key/value pairs called maps (called hash table, hash map, dictionary or associative array in other languages).

You create a map by using the keyword map and defining a type for keys and type for values map[Tk]Tv, for example a map with keys as strings and values as integers can be defined as:

var counts map[string]int

You can assign values to a map just like arrays by using curly braces {...} where keys and values are separated by a colon :, for example:

counts := map[string]int{
  "Apples": 4,
  "Oranges": 7,
}

Maps can be indexed by their keys just like arrays/slices:

counts["Apples"]  // 4

And iterated over similar to array/slices:

for key, value := range counts {
  fmt.Printf("%s: %d\n", key, value)
}

The only important thing to note about maps in Go is you must initialize a map before using it, a nil map will cause a program error and panic:

var counts map[string]int
counts["Apples"] = 7  // This will cause an error and panic!

You must initialize a map before use by using the make() function:

counts := make(map[string]int)
counts["Apples"] = 7

Flow control structures

Go only has one looping construct as seen in the previous sections:

sum := 0
for i := 0; i < 10; i++ {
  sum += i
}

The basic for loop has three components separated by semicolons:

  • the init statement: executed before the first iteration
  • the condition expression: evaluated before every iteration
  • the post statement: executed at the end of every iteration

If you omit the condition you effectively have an infinite loop:

for {
}
// This line is never reached!

Go has the usual if statement along with else if and else for branching:

N := 42
func Guess(n int) string {
  if n == 42 {
    return "You got it!"
  } else if n < N {
    return "Too low! Try again..."
  } else {
    return "Too high! Try again..."
  }
}        

Note: The last else could have been omitted and been written as return "Too high~ Try again...", as it would have been functionally equivalent.

There is also a switch statement that can be used in place of multiple if and else if statements, for example:

func FizzBuzz(n int) string {
  switch n {
  case n % 15 == 0:
    return "FizzBuzz"
  case n % 3 == 0:
    return "Fizz"
  case n % 5 == 0:
    return "Buzz"
  default:
    return fmt.Sprintf("%d", n)
  }
}

Functions can be executed at the end of a function anywhere in your function by "deferring" their execution by using the defer keyword. This is commonly used to close resources automatically at the end of a function, for example:

package main

import (
  "os"
  "fmt"
)

func Goodbye(name string) {
  fmt.Printf("Goodbye %s", name)
}

func Hello(name string) {
  defer Goodbye(name)
  fmt.Printf("Hello %s", name)
}

func main() {
  user := os.Getenv("User")
  Hello(user)
}

This will output when run:

$ ./hello
Hello prologic
Goodbye prologic

Error handling

Errors are values in Go and you return them from functions. For example opening a file with os.Open returns a pointer to the open file and nil error on success, otherwise a nil pointer and the error that occurred:

f, err := os.Open("/path/to/file")

You check for errors like any other value:

f, err := os.Open("/path/to/file")
if err == nil {
  // do something with f
}

It is idiomatic Go to check for non-nil errors from functions and return early, for example:

func AppendFile(fn, text string) error {
  f, err := os.OpenFile(fn, os.O_CREATE|os.O_APPEND|os.WR_ONLY, 0644)
  if err != nil {
    return fmt.Errorf("error opening file for writing: %w", err)
  }
  defer f.Close()
  
  if _, err := f.Write([]byte(text)); err != nil {
    return fmt.Errorf("error writing text to fiel: %w", err)
  }
  
  return nil
}

Creating and import packages

Finally Go (like every other decent languages) has a module system where you can create packages and import them. We saw earlier In Basics how we create a module with go mod init when starting a new project.

Go packages are just a directory containing Go source code. The only difference is the top-line of each module (each *.go source file):

Create a Go package by first creating a directory for it:

$ mkdir shapes

And initializing it with go mod init:

$ cd shapes
$ go mod init github.com/prologic/shapes

Now let's create a source module called circle.go using our favorite editor:

package shapes

type Circle struct {
  Radius float64
}

func (c Circle) String() string {
  return fmt.Sprintf("Circle(%0.2f)", c.Radius)
}

func (c Circle) Area() float64 {
  return 3.14 * c.Radius * c.Radius
}

It is important to note that in order to "export" functions, structs or package scoped variables or constants, they must be capitalized or the Go compiler will not export those symbols and you will not be able access them from importing the package.

Now create a Git repository on Github called "shapes" and push your package to it:

$ git init
$ git commit -a -m "Initial Commit"
$ git remote add origin git@github.com:prologic/shapes.git
$ git push -u origin master

You can import the new package shapes by using it's fully qualified "importpath" as github.com/prologic/shapes. Go automatically knows hot to fetch and build the package given its import path.

Example:

Let's create a simple program using the package github.com/prologic/shapes:

$ mkdir hello
$ go mod init hello

And let's write the code for main.go using our favorite editor:

package main

import (
  "fmt"

  "github.com/prologic/shapes"
)

func main() {
  c := shapes.Circle{Radius: 5}
  fmt.Printf("Area of %s: %0.2f\n", c, c.Area())
}

Building it with go build:

$ go build

And finally let's test it out by running the resulting binary:

$ ./hello
Area of Circle(5.00): 78.50

Congratulations! 🎉

Now you're a Gopher!

That's it! Now you know a fairly decent chunk of Go. Some (pretty important) things I didn't cover include:

  • Writing unit tests, writing tests in Go is really easy! See testing
  • The standard library, Go has a huge amount of useful packages in the standard library. See Standard Library.
  • Goroutines and Channels, Go's builtin concurrency is really powerful and easy to use. See Concurrency.
  • Cross-Compilation, compiling your program for other architectures and operating systems is super easy. Just set the GOOS and GOARCH environment variables when building.

For more details, check the latest documentation, or for a less half-baked tutorial, please read the official Go Tutorial and A Tour of Go.

Other great tutorials you can read:

@prologic
Copy link
Author

prologic commented Jan 4, 2021

Thank you @Krishna for your quick feedback and thorough read through! 🙇‍♂️ I wrote this up pretty quickly last night (probably too quickly) and some of the examples by hand. Thanks for spotting the errors 👍 I've fixed what I could find.

@macleginn
Copy link

Typo: "Sliaces"

@Kugelschieber
Copy link

Kugelschieber commented Jan 5, 2021

You probably want to add the delete function for maps: https://golang.org/pkg/builtin/#delete

Oh and you're in place 1 on Hacker News right now: https://news.ycombinator.com/item?id=25646909

@moqmar
Copy link

moqmar commented Jan 5, 2021

This is awesome! What I'm missing is that maps and slices must be initialized (and maybe the difference between make(...) and ...{}). And you typoe'd "Sliaces" somewhere. Also, Goroutines, Channels and the "select" keyword might deserve a short section for when working with parallelized code. And in many places a reference to the official Godoc would be really useful, like for the strings package.

@ForsakenHarmony
Copy link

Interfaces also seem to be missing

@mewmew
Copy link

mewmew commented Jan 5, 2021

Looks like a good quick start for anyone getting into the language.

Found a minor typo, wanted to send a PR but no idea how to do that with Gist.

https://gist.github.com/mewmew/1138388627593d1b2fe76e1d2425aaee/revisions#diff-b6a65197a12017df5aa2653738a2f7d874d945935847a8ca7ffa6421bdab6392L331

-  switch n {
+  switch {
   case n % 15 == 0:
     return "FizzBuzz"
   case n % 3 == 0:

@CaveSven
Copy link

CaveSven commented Jan 5, 2021

Thanks!
Spotted one other typo:

func (a *Account) Deposit(amt flaot64) float64 {

should be

func (a *Account) Deposit(amt float64) float64 {

@fernandoacorreia
Copy link

Typo:

return fmt.Sprintf("Account[%d]: $0.2f", a.id, a.bal)

should be

return fmt.Sprintf("Account[%d]: %0.2f", a.id, a.bal)

@changkun
Copy link

changkun commented Jan 5, 2021

What's introduced here are commonly exists in every language, these are the things that developers can easily convey from other languages. However, features such as interface, goroutines, and channels those language key features are the ones IMO should be covered as essentials whereas is missing from what was overclaimed: "learn X in Y min".

@redjoker011
Copy link

Thanks for the work. Newbie in go like me can use this as reference whenever we need a refresher for the basic, though what I think missing and good to have were some examples on using Pointers especially when passing as function argument.

@williamfalconeruk
Copy link

I think this is a great primer, though i do think interfaces should be mentioned here as many languages also use that concept quite heavily. A link to some examples might be useful here.

@abishekmuthian
Copy link

I wonder whether adding struct tags could be an useful addition, considering someone looking to learn Go using this might be doing it for a quick network job and tags are very useful for even for a simple REST marshalling.

@Joshfindit
Copy link

One quick note from a junior perspective:

func hello(name string) string {
...
Functions with a return type must explicitly return a value.

By by picking it up from examples later it’s clear that string is the return type, but it would help to be explicit about the func template here.

@redjoker011
Copy link

I wonder whether adding struct tags could be an useful addition, considering someone looking to learn Go using this might be doing it for a quick network job and tags are very useful for even for a simple REST marshalling.

Good point some examples were really helpful to especially when working with XML and JSON in particular.

@artiom
Copy link

artiom commented Jan 6, 2021

circle.go is missing import "fmt" after the package shapes line.

@mic4ael
Copy link

mic4ael commented Jan 8, 2021

Typo in this sentence Sliaces are created by omitting the size:

@mic4ael
Copy link

mic4ael commented Jan 8, 2021

You can import the new package shapes by using it's its fully qualified "importpath"

@ojej
Copy link

ojej commented Jan 10, 2021

Poprosze po polsku

@prologic
Copy link
Author

A note from the author (that's me!). Thank you all so much for the very positive overwhelming feedback! Please watch this space for updates, here's what I plan to do over the next coming days:

Thank you! 🙇‍♂️

@hholst80
Copy link

I wonder whether adding struct tags could be an useful addition, considering someone looking to learn Go using this might be doing it for a quick network job and tags are very useful for even for a simple REST marshalling.

Good point some examples were really helpful to especially when working with XML and JSON in particular.

I agree that this is super important for people coming from Javascript and other dynamic languages. However, it should probably be a separate 102 on top of this one. It is already way more than 5 minutes. :-)

@victorhurdugaci
Copy link

This is awesome! May I suggest adding links to working examples in go playground (where possible)?

@tclemos
Copy link

tclemos commented Jan 13, 2021

5 minutes? really? Click bait has been planted, LoL!

@prologic
Copy link
Author

5 minutes? really? Click bait has been planted, LoL!

Yeah sorry about that! It's actually more like ~10mins. My Markdown editor claims 5, but Firefox claims 9. 🤷‍♂️

@tclemos
Copy link

tclemos commented Jan 14, 2021

5 minutes? really? Click bait has been planted, LoL!

Yeah sorry about that! It's actually more like ~10mins. My Markdown editor claims 5, but Firefox claims 9. 🤷‍♂️

In this case you must change LEARN by READ, because this is the READING time, not even close to the amount of time needed to assimilate/learn everything!

It is indeed a click bait!

If you really want something to "learn" Go quickly, please, watch this: https://www.youtube.com/watch?v=C8LgvuEBraI

you can watch this in 2x, so it's 6 minutes lmao

@sunwicked
Copy link

Requesting to add in aquadzn/learn-x-by-doing-y#8

@vietvudanh
Copy link

vietvudanh commented Jan 23, 2021

What's introduced here are commonly exists in every language, these are the things that developers can easily convey from other languages. However, features such as interface, goroutines, and channels those language key features are the ones IMO should be covered as essentials whereas is missing from what was overclaimed: "learn X in Y min".

I agree. For me "A tour of go" covers what needed at the very least level.

@AlphaHot
Copy link

Dude Just read the Golang specification

@whihathac
Copy link

Great work! I used this gist in conjunction with Jake Wright's Youtube video of learning Go in 12 mins (https://www.youtube.com/watch?v=C8LgvuEBraI).

1 correction: "Go automatically knows hot to fetch and build the package given its import path."

@deulizealand
Copy link

Please update this tutorial.

maybe after 1 year of createing this, you must extends this to 15 mins. Thanks !

@rxx
Copy link

rxx commented Apr 17, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment