Go Programming Language

· 2704 words · 13 minute read

Go in a Nutshell is an imperative language, and statically typed. Its syntax tokens are similar to C (but less parentheses and no semicolons) and the structure to Oberon-2. Go compiles to native code (no JVM). No classes, but structs with methods. Interfaces to encourage protocol-oriented development. No implementation of inheritance. There’s type embedding, though. Functions are first class citizens. Functions can return multiple values. Go has closures. Pointers, but not pointer arithmetic. Built-in concurrency primitives: Goroutines and Channels.

Basic Syntax of Go Programming Language 🔗

Hello World Example 🔗

First of all, create a directory/folder, and go inside it, then initialize a module in Go like this.

go mod init github.com/USERNAME/PROJECT-NAME

That will create the go.mod file.

Then create a file named main.go like this.

touch main.go

Write this code inside main.go file using your prefered code editor.

package main

import "fmt"

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

Then save the changes and run this command in terminal. Make sure you are in the directory/folder of the go project.

go run main.go

that will run the code and print Hello, Go! in the output.

You can run the Go project with this succinct command instead.

go run .

If you want to build the project as executable program use this command.

go build .

The project name will be main but if you want to specify a specific name for the program use the -o flag to set it.

go build -o appname .

To run the executable program in the terminal, add the dot and slash before it like this.

./appname

If your Go project has any dependencies outside the standard library, get the dependencies before running or building the project.

go mod tidy

Operators 🔗

Arithmetic Operators 🔗

OperatorDescription
+addition
-subtraction
*multiplication
/quotient
%remainder
&bitwise and
|bitwise or
^bitwise xor
&^bit clear (and not)
«left shift
>>right shift

Comparison Operators 🔗

OperatorDescription
==equal
!=not equal
<less than
<=less than or equal
>greater than
>=greater than or equal

Logical Operators 🔗

OperatorDescription
&&logical and
||logical or
!logical not

Other 🔗

OperatorDescription
&address of / create pointer
*dereference pointer
<-send / receive operator (see ‘Channels’ below)

Declarations 🔗

Type goes after identifier!

var foo int // declaration without initialization
var foo int = 42 // declaration with initialization
var foo, bar int = 42, 1302 // declare and init multiple vars at once
var foo = 42 // type omitted, will be inferred
foo := 42 // shorthand, only in func bodies, omit var keyword, type is always implicit
const constant = "This is a constant"

Functions 🔗

// a simple function
func functionName() {}

// function with parameters (again, types go after identifiers)
func functionName(param1 string, param2 int) {}

// multiple parameters of the same type
func functionName(param1, param2 int) {}

// return type declaration
func functionName() int {
    return 42
}

// Can return multiple values at once
func returnMulti() (int, string) {
    return 42, "foobar"
}
var x, str = returnMulti()

// Return multiple named results simply by return
func returnMulti2() (n int, s string) {
    n = 42
    s = "foobar"
    // n and s will be returned
    return
}
var x, str = returnMulti2()

Functions As Values And Closures 🔗

func main() {
    // assign a function to a name
    add := func(a, b int) int {
        return a + b
    }
    // use the name to call the function
    fmt.Println(add(3, 4))
}

// Closures, lexically scoped: Functions can access values that were
// in scope when defining the function
func scope() func() int{
    outer_var := 2
    foo := func() int { return outer_var}
    return foo
}

func another_scope() func() int{
    // won't compile because outer_var and foo not defined in this scope
    outer_var = 444
    return foo
}

// Closures
func outer() (func() int, int) {
    outer_var := 2
    inner := func() int {
        outer_var += 99 // outer_var from outer scope is mutated.
        return outer_var
    }
    inner()
    return inner, outer_var // return inner func and mutated outer_var 101
}

Variadic Functions 🔗

func main() {
 fmt.Println(adder(1, 2, 3))  // 6
 fmt.Println(adder(9, 9)) // 18

 nums := []int{10, 20, 30}
 fmt.Println(adder(nums...)) // 60
}

// By using ... before the type name of the last parameter you can indicate that it takes zero or more of those parameters.
// The function is invoked like any other function except we can pass as many arguments as we want.
func adder(args ...int) int {
 total := 0
 for _, v := range args { // Iterates over the arguments whatever the number.
  total += v
 }
 return total
}

Built-in Types 🔗

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32 ~= a character (Unicode code point) - very Viking

float32 float64

complex64 complex128

Type Conversions 🔗

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

// alternative syntax
i := 42
f := float64(i)
u := uint(f)

Packages 🔗

  • Package declaration at top of every source file
  • Executables are in package main
  • Convention: package name == last name of import path (import path math/rand => package rand)
  • Upper case identifier: exported (visible from other packages)
  • Lower case identifier: private (not visible from other packages)

Control Structures 🔗

if 🔗

func main() {
 // Basic one
 if x > 10 {
  return x
 } else if x == 10 {
  return 10
 } else {
  return -x
 }
 
 // You can put one statement before the condition
 if a := b + c; a < 42 {
  return a
 } else {
  return a - 42
 }
 
 // Type assertion inside if
 var val interface{}
 val = "foo"
 if str, ok := val.(string); ok {
  fmt.Println(str)
 }
}

Loops 🔗

// There's only `for`, no `while`, no `until`
for i := 1; i < 10; i++ {
}
for ; i < 10;  { // while - loop
}
for i < 10  { // you can omit semicolons if there is only a condition
}
for { // you can omit the condition ~ while (true)
}

Switch 🔗

// switch statement
switch operatingSystem {
case "darwin":
    fmt.Println("Mac OS Hipster")
    // cases break automatically, no fallthrough by default
case "linux":
    fmt.Println("Linux Geek")
default:
    // Windows, BSD, ...
    fmt.Println("Other")
}

// as with for and if, you can have an assignment statement before the switch value
switch os := runtime.GOOS; os {
case "darwin": ...
}

// you can also make comparisons in switch cases
number := 42
switch {
    case number < 42:
        fmt.Println("Smaller")
    case number == 42:
        fmt.Println("Equal")
    case number > 42:
        fmt.Println("Greater")
}

// cases can be presented in comma-separated lists
var char byte = '?'
switch char {
    case ' ', '?', '&', '=', '#', '+', '%':
        fmt.Println("Should escape")
}

Arrays, Slices, Ranges 🔗

Arrays 🔗

var a [10]int // declare an int array with length 10. Array length is part of the type!
a[3] = 42     // set elements
i := a[3]     // read elements

// declare and initialize
var a = [2]int{1, 2}
a := [2]int{1, 2} //shorthand
a := [...]int{1, 2} // elipsis -> Compiler figures out array length

Slices 🔗

var a []int                              // declare a slice - similar to an array, but length is unspecified
var a = []int {1, 2, 3, 4}               // declare and initialize a slice (backed by the array given implicitly)
a := []int{1, 2, 3, 4}                   // shorthand
chars := []string{0:"a", 2:"c", 1: "b"}  // ["a", "b", "c"]

var b = a[lo:hi] // creates a slice (view of the array) from index lo to hi-1
var b = a[1:4]  // slice from index 1 to 3
var b = a[:3]  // missing low index implies 0
var b = a[3:]  // missing high index implies len(a)
a =  append(a,17,3) // append items to slice a
c := append(a,b...) // concatenate slices a and b

// create a slice with make
a = make([]byte, 5, 5) // first arg length, second capacity
a = make([]byte, 5) // capacity is optional

// create a slice from an array
x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // a slice referencing the storage of x

Operations on Arrays and Slices 🔗

len(a) gives you the length of an array/a slice. It’s a built-in function, not a attribute/method on the array.

// loop over an array/a slice
for i, e := range a {
    // i is the index, e the element
}

// if you only need e:
for _, e := range a {
    // e is the element
}

// ...and if you only need the index
for i := range a {
}

// In Go pre-1.4, you'll get a compiler error if you're not using i and e.
// Go 1.4 introduced a variable-free form, so that you can do this
for range time.Tick(time.Second) {
    // do it once a sec
}

Maps 🔗

var m map[string]int
m = make(map[string]int)
m["key"] = 42
fmt.Println(m["key"])

delete(m, "key")

elem, ok := m["key"] // test if key "key" is present and retrieve it, if so

// map literal
var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

Structs 🔗

There are no classes, only structs. Structs can have methods.

// A struct is a type. It's also a collection of fields

// Declaration
type Vertex struct {
    X, Y int
}

// Creating
var v = Vertex{1, 2}
var v = Vertex{X: 1, Y: 2} // Creates a struct by defining values with keys
var v = []Vertex{{1,2},{5,2},{5,5}} // Initialize a slice of structs

// Accessing members
v.X = 4

// You can declare methods on structs. The struct you want to declare the
// method on (the receiving type) comes between the the func keyword and
// the method name. The struct is copied on each method call(!)
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// Call method
v.Abs()

// For mutating methods, you need to use a pointer (see below) to the Struct
// as the type. With this, the struct value is not copied for the method call.
func (v *Vertex) add(n float64) {
    v.X += n
    v.Y += n
}

Anonymous structs: Cheaper and safer than using map[string]interface{}.

point := struct {
 X, Y int
}{1, 2}

Pointers 🔗

p := Vertex{1, 2}  // p is a Vertex
q := &p            // q is a pointer to a Vertex
r := &Vertex{1, 2} // r is also a pointer to a Vertex

// The type of a pointer to a Vertex is *Vertex

var s *Vertex = new(Vertex) // new creates a pointer to a new struct instance

Interfaces 🔗

// interface declaration
type Awesomizer interface {
    Awesomize() string
}

// types do *not* declare to implement interfaces
type Foo struct {}

// instead, types implicitly satisfy an interface if they implement all required methods
func (foo Foo) Awesomize() string {
    return "Awesome!"
}

Embedding 🔗

There is no subclassing in Go. Instead, there is interface and struct embedding.

// ReadWriter implementations must satisfy both Reader and Writer
type ReadWriter interface {
    Reader
    Writer
}

// Server exposes all the methods that Logger has
type Server struct {
    Host string
    Port int
    *log.Logger
}

// initialize the embedded type the usual way
server := &Server{"localhost", 80, log.New(...)}

// methods implemented on the embedded struct are passed through
server.Log(...) // calls server.Logger.Log(...)

// the field name of the embedded type is its type name (in this case Logger)
var logger *log.Logger = server.Logger

Errors 🔗

There is no exception handling. Functions that might produce an error just declare an additional return value of type Error.

This is the Error interface:

type error interface {
    Error() string
}

A function that might return an error:

func doStuff() (int, error) {
}

func main() {
    result, err := doStuff()
    if err != nil {
        // handle error
    } else {
        // all is good, use result
    }
}

Concurrency 🔗

Goroutines 🔗

Goroutines are lightweight threads (managed by Go, not OS threads). go f(a, b) starts a new goroutine which runs f (given f is a function).

// just a function (which can be later started as a goroutine)
func doStuff(s string) {
}

func main() {
    // using a named function in a goroutine
    go doStuff("foobar")

    // using an anonymous inner function in a goroutine
    go func (x int) {
        // function body goes here
    }(42)
}

Channels 🔗

ch := make(chan int) // create a channel of type int
ch <- 42             // Send a value to the channel ch.
v := <-ch            // Receive a value from ch

// Non-buffered channels block. Read blocks when no value is available, write blocks until there is a read.

// Create a buffered channel. Writing to a buffered channels does not block if less than <buffer size> unread values have been written.
ch := make(chan int, 100)

close(ch) // closes the channel (only sender should close)

// read from channel and test if it has been closed
v, ok := <-ch

// if ok is false, channel has been closed

// Read from channel until it is closed
for i := range ch {
    fmt.Println(i)
}

// select blocks on multiple channel operations, if one unblocks, the corresponding case is executed
func doStuff(channelOut, channelIn chan int) {
    select {
    case channelOut <- 42:
        fmt.Println("We could write to channelOut!")
    case x := <- channelIn:
        fmt.Println("We could read from channelIn")
    case <-time.After(time.Second * 1):
        fmt.Println("timeout")
    }
}

Channel Axioms 🔗

A send to a nil channel blocks forever

var c chan string
c <- "Hello, World!"
// fatal error: all goroutines are asleep - deadlock!

A receive from a nil channel blocks forever

var c chan string
fmt.Println(<-c)
// fatal error: all goroutines are asleep - deadlock!

A send to a closed channel panics

var c = make(chan string, 1)
c <- "Hello, World!"
close(c)
c <- "Hello, Panic!"
// panic: send on closed channel

A receive from a closed channel returns the zero value immediately

var c = make(chan int, 2)
c <- 1
c <- 2
close(c)
for i := 0; i < 3; i++ {
    fmt.Printf("%d ", <-c)
}
// 1 2 0

Printing 🔗

fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // basic print, plus newline
p := struct { X, Y int }{ 17, 2 }
fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etc
s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print to string variable

fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish format
s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // formatted print to string variable

hellomsg := `
 "Hello" in Chinese is 你好 ('Ni Hao')
 "Hello" in Hindi is नमस्ते ('Namaste')
` // multi-line string literal, using back-tick at beginning and end

Reflection 🔗

Type Switch 🔗

A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.

func do(i interface{}) {
 switch v := i.(type) {
 case int:
  fmt.Printf("Twice %v is %v\n", v, v*2)
 case string:
  fmt.Printf("%q is %v bytes long\n", v, len(v))
 default:
  fmt.Printf("I don't know about type %T!\n", v)
 }
}

func main() {
 do(21)
 do("hello")
 do(true)
}

Code Snippets 🔗

HTTP Server 🔗

package main

import (
    "fmt"
    "net/http"
)

// define a type for the response
type Hello struct{}

// let that type implement the ServeHTTP method (defined in interface http.Handler)
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

func main() {
    var h Hello
    http.ListenAndServe("localhost:4000", h)
}

// Here's the method signature of http.ServeHTTP:
// type Handler interface {
//     ServeHTTP(w http.ResponseWriter, r *http.Request)
// }

Finally 🔗

That’s all right now. I will add more code samples and snippets later (I update it monthly).

I am working on a video to introduce you to Go. Subscribe to the YouTube channel to see it first.