Go Programming

Internet Engineering
Spring 2024
@1995parham

Gopher

real-gopher gophers
go-vs-java
gopher-riding-rex

Go is

  • Modern
  • Generic Purpose
  • Open Source

Programming Language

  • Officially announced at November 10, 2009
  • It began as an internal Google project
  • The designers were primarily motivated by their shared dislike of C++
  • Its spiritual fathers are
    • Robert Griesemer
    • Ken Thomson
    • Rob Pike
  • version 1.0 was released in March 2012.
  • Go is widely used in production at Google and in many other organizations and open-source projects.
  • Lack of support for generic programming and the verbosity of error handling in Go 1.x had drawn considerable criticism.
gopher-rocks
  • Go is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency.
  • There are two major implementations:
    1. Google's self-hosting compiler toolchain targeting multiple operating systems, mobile devices, and WebAssembly.
    2. gccgo, a GCC frontend.

memory safety


int a[10];
a[11] = 10;
    

garbage collection


void allocate_forever(void) {
  int *a = malloc(10 * sizeof(int));
  a[0] = 10;
}
    

structural typing


struct student {
  int  id;
  char surname[255];
  char forename[255];
};
    
  • Go code is easy to read and easy to understand
  • Go does not have a preprocessor
  • Go uses static linking by default, which means that the binary files produced can be easily transferred to other machines with the same OS.

preprocessor


#define students_foreach_loop(stds, element) \
    struct students_el *element ## _el = stds->head; \
    const struct student *element = element ## _el->student; \
    for (; element ## _el != NULL; element ## _el = element ## _el->next, element = element ## _el ? element ## _el->student : NULL)

  students_foreach_loop(stds, el) {
    fprintf(fp, "Name: %s\n", el->name);
    fprintf(fp, "ID: %s\n", el->id);
    fprintf(fp, "\n");
  }
    
dyn-static
  • A syntax and environment adopting patterns more common in dynamic languages:
    • Optional concise variable declaration and initialization through type inference (x := 0 instead of int x = 0;).
    • Fast compilation.
    • Remote package management (go get) and online package documentation.
  • Distinctive approaches to particular problems:
    • Built-in concurrency primitives: light-weight processes (goroutines), channels, and the select statement.
    • An interface system in place of virtual inheritance, and type embedding instead of non-virtual inheritance.
    • A toolchain that, by default, produces statically linked native binaries without external dependencies.
  • A desire to keep the language specification simple enough to hold in a programmer's head, in part by omitting features that are common in similar languages.
teaching-gopher

package main

import "fmt"

func main() {
  fmt.Printf("Hello, دنیا\n")
}
  
  • Go code is organized into packages, which are similar to libraries or modules in other languages.
  • The import declaration must follow the package declaration.
  • You must import exactly the packages you need.
  • A function declaration consists of the keyword func, the name of the function, a parameter list (empty for main), a result list (also empty here), and the body of the function - the statements that define what it does - enclosed in braces.

<Code Time.go>

  • Write Hello World in Go but Before that we must install the Go! and our favorite text editor

Tip!

  • Use Code Time.go links to see the source code
  • Use curl to download the source code

curl https://raw.githubusercontent.com/1995parham-teaching/go-lecture/main/00-hello-world/main.go > hello-world.go
    

Constants

Maintained precisely:


const e = 2.71828182845904523536028747135266249775724709369995957496696763
const third = 1/3
  

Typed or without type:


const M64 int64 = 1<<20
const M = 1<<20
  

Evaluated at compile-time:


const big = 1<<100 / 1e30  // valid constant expression
  

Variables

var name type = expression

name := expression // inside functions only

Statically typed:


var x int
var s, t string
    

Implicitly or explicitly initialized:


var x int                       // x = 0
var s, t string = "foo", "bar"  // multiple assignment

var x = 42                      // int
var s, b = "foo", true          // string, bool
    

Short variable declaration:


x := 42
s, b := "foo", true
    

Pointers ⚰️

  • A pointer value is the address of a variable.
  • Not every value has an address, but every variable does.
  • With a pointer, we can read or update the value of a variable indirectly

x := 1
p := &x     // p, of type *int, points to x
fmt.Println(*p) // 1
*p = 2          // equivalent to x = 2
fmt.Println(x)  // 2
    

Predeclared Name

which can be reused


true false iota nil
    

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
    

make len cap new append copy close delete
complex real imag
panic recover
    

which can't be reused


break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
    

<Code Time.go>

  • Let define variables and constants to see how their work

Calculation


x := 42
y := x + 10
var c = (f - 32) * 5 / 9

var x uint8 = 1 << 1 | 1 << 5
var y uint8 = 1 << 1 | 1 << 2

fmt.Printf("%08b\n", x) // "00100010"
fmt.Printf("%08b\n", y) // "00000110"

fmt.Printf("%08b\n", x&y) // "00000010"
fmt.Printf("%08b\n", x|y)     // "00100110"
fmt.Printf("%08b\n", x^y)     // "00100100"
  

<Code Time.go>

  • Do some math

The new Function

  • Another way to create a variable is to use the built-in function new
  • The expression new(T) create an unnamed variable of type T, initializes it to the zero value of T, and return its address, which is a value of type *T
  • The new function is relatively rarely used.

func newInt() *int {
  return new(int)
}

func newInt() *int {
  var dummy int
  return &dummy
}
    

Assignment

  • Tuple Assignment
    • Allows several variables to be assigned at once.
    • All of the right-hand side expressions are evaluated before any of the variable are updated.

// tuple assignment

a, b = b, a // swap

f, err = os.Open(filename) // multiple assignment

func gcd(x, y int) int {
  for y != 0 {
    x, y = y, x % y
  }

  return x
}
  

Statements

  • Curly braces (C style)
  • Multiple assignments and some other new constructs
  • Statements are not Expressions.
  • Many cleanups 🧹:
    • mandatory braces
    • no parentheses for conditionals
    • implicit break in switches
    • no semicolons
    • ...

Statements



if x < y {
  return x
} else {
  return y
}

switch day {
case Mon:
  ...
  // break is implicit
case Tue, Wed:
  ...
}
  
  • Not shown: break, goto, continue, fallthrough

Statements

  • Unified for syntax

// a traditional infinite loop
for {
  // ...
}
    

for initialization; condition; post {
  // zero or more statements
}
    

// a traditional "while" loop
for condition {
  // ...
}
    
  • range over arrays, slices, and maps
  • In each iteration of the loop, range produces a pair of values: the index and the value of the element at that index.
  • If you don't want the index variable you can use the blank identifier, whose name is _ (that is, an underscore).

for i, num := range numbers { ... }
for city, pop := range population { ... }
    

Functions


package main

import "fmt"

func Fibonacci(n int) int {
  if n == 0 || n == 1 {
    return 1
  } else {
    return Fibonacci(n-1) + Fibonacci(n-2)
  }
}

func main() {
  fmt.Printf("%d\n", Fibonacci(10))
}
  

<Code Time.go>

  • Conditions with taste of Fibonacci 😋

<Code Time.go>

  • Many loops

Interaction


package "main"

import "fmt"

func main() {
  var n int
  fmt.Scanf("%d", &n)
  fmt.Printf("%d\n", n)
}
    
%ddecimal integer
%x, %o, %binteger in hexadecimal, octal, binary
%f, %g, %efloating-point number: 3.141593 3.141592653589793 3.141593e+00
%tboolean: true or false
%crune (Unicode code point)
%sstring
%qquoted string "abc" or rune 'c'
%vany value in natrual format
%Ttype of any value
%%literal percent sign (no operand)

Strings

  • A string is an immutable sequence of bytes
  • Text strings are conventionally interpreted as UTF-8 encoded sequence of Unicode code points (runes)
  • The built-in len function the number of bytes (not runes) in a string, and the index operation s[i] retrieves the i-th byte of string s, where 0 <= i < len(s)

s := "hello, world"
fmt.Println(len(s)) // 12
fmt.Println(s[0], s[7]) // 'h' and 'w'
    
  • Constructions that try to modify a string's data in place are not allowed:
  • 
    s[0] = 'L' // compile error: cannot assign to s[0]
          
  • Four standard packages are particularly important for manipulating strings:
    • bytes
    • strings
    • strconv
    • unicode

go doc bytes
    

Unicode

  • unicode.org
  • Which collects all of the characters in all of the world's writing systems, plus accents and other diacritical marks, control codes like tab and carriage return, etc.

Conversions between Strings and Numbers

  • To convert an integer to a string, one option is to use fmt.Sprintf; another is to use the function strconv.Itoa.
  • strconv.FormatInt() and strconv.FormatUint can be used to format numbers in a different base
  • The fmt.Printf verbs %b, %d, %o, and %x are often more convenient thatn Format functions

x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"
    
  • To parse a string representing an integer, use the strconv.Atoi or strconv.ParseInt, or strconv.ParseUint for unsigned integers
  • The third argument of ParseInt gives the size of the integer type that the result must fit into
  • In any case, the type of the result is always int64, which you can then convert to a smaller type
  • fmt.Sscanf is useful for parsing input that consists of orderly mixtures of strings and number all on a single line

x, err := strconv.Atoi("123")              // x is an int
y, err := strconv.ParseInt("123", 10, 64)  // base 10, up to 64 bits
    

<Code Time.go>

    Arrays

    • An array is a fixed-length sequence of zero or more elements of a particular type.
    • Because of their fixed length, arrays are rarely used directly in Go.
    • By default elements of a new array array variable are initally set to zero value for the element type, which is 0 for numbers.
    
    var a [3]int                // array of 3 integers
    fmt.Println(a[0])           // print the first element
    fmt.Println(a[len(a) - 1])  // print the last element, a[2]
        
    
    a := [2]int{1, 2}
    b := [...]int{1, 2}
    c := [2]int{1, 3}
    
    fmt.Println(a == b, a == c, b == c) // "true false false"
    
    d := [3]int{1, 2}
    fmt.Prinln(a == d) // compile error: cannot compare [2]int == [3]int
        
    
    // Print the indices and elements.
    for i, v := range a {
      fmt.Printf("%d %d\n", i, v)
    }
    
    // Print the elements only.
    for _, v := range a {
      fmt.Printf("%d\n", v)
    }
        

    Slices

    
    []T  // slice of T
          
    • Descriptor for an underlying array segment
    • May grow and shrink
    • Has length and capacity
    • Assigning a slice copies the descriptor, not the underlying array
    • Common slice operations:
      • The slice operators[i:j], where 0 <= i <= j <= cap(s), creates a new slice that refers to elements i through j - 1 of the sequence s.
      
      len(s)
      s[i]
      s[i:j]
      append(s, x)  // append element x to slice s and return new slice
            
    • Slices play the role of dynamically sized arrays
    • Widely used in Go code

    <Code Time.go>

    • Arrays

    <Code Time.go>

    • Slices with make

    <Code Time.go>

    • Slice operations

    Structures

    
    package main
    
    type Sample struct {
      S1      int
      S2      int
      S3      string
      private float64
    }
    
    func main() {
      var smp Sample
      smp.S1 = 10
      smp.S2 = 20
      smp.S3 = "Hello World"
    }
      
    • A struct is an aggregate data type that groups together zero or more named values of arbitrary types as a single entity
    • Each value is called a field
    • C Structures 🕺
    • Data hiding:
      • The name of a struct field is exported if it begins with a capital letter; this is Go's main access control mechanism.
      • S1, S2, S3 are public and can be accessed from anywhere.
      • private is private and is only visible to code in the same package.

    Methods

    
    package main
    
    import "fmt"
    
    type Example struct {
      Val   string
      count int
    }
    
    // define a custom type based on go standard types
    type integer int
    
    func (i integer) log() {
      fmt.Printf("%d\n", i)
    }
    
    // pointer reciever which can change 'example' fields
    func (e *Example) Log() {
      e.count++
      fmt.Printf("%d %s\n", e.count, e.Val)
    }
    
    func main() {
      var i integer
      exm := Example{
        Val:   "Example",
        count: 10}
      i.log()
      exm.Log()
    }
        

    <Code Time.go>

      Interfaces

      
      package main
      
      import "fmt"
      
      type Printer interface {
        Print()
      }
      
      type Foo struct {
        X, Y int
      }
      
      type Bar struct {
        X, Y float64
      }
      
      func (f Foo) Print() {
        fmt.Printf("%d %d\n", f.X, f.Y)
      }
      
      func (b Bar) Print() {
        fmt.Printf("%g %g\n", b.X, b.Y)
      }
        

      <Code Time.go>

        Casting Types

        • type conversion: A type conversion is similar to a cast in C. It reinterprets the value as a new type
        
        package main
        
        func main() {
          one := 1
          var float float32
          float = float32(one)
        }
          

        Type Assertion

        • type assertion: They do not convert between types; they simply state to the compiler that the underlying value has the specific type.
        
        package main
        
        import "fmt"
        
        type empty interface{}
        
        type example struct {
          A int
        }
        
        func main() {
          one := 1
          var i empty = one
          j := example{A: 10}
          var k empty = j
          fmt.Println(k.(example).A)
        }
          
        nerdy

        Create Point Interface

        
        type Point interface {
          Distance() float64
          ImageOnX() float64
          ImageOnY() float64
        }
            

        Create Cartesian Point Structure

        
        type Cartesian struct {
          X float64
          Y float64
        }
        
        func (c *Cartesian) Distance() float64 {
          return math.Sqrt(c.X*c.X + c.Y*c.Y)
        }
        
        func (c *Cartesian) ImageOnX() float64 {
          return c.X
        }
        
        func (c *Cartesian) ImageOnY() float64 {
          return c.Y
        }
            

        Create Polar Point Structure

        
        type Polar struct {
          R    float64
          Teta float64
        }
        
        func (p *Polar) Distance() float64 {
          return p.R
        }
        
        func (p *Polar) ImageOnX() float64 {
          return p.R * math.Cos(p.Teta)
        }
        
        func (p *Polar) ImageOnY() float64 {
          return p.R * math.Sin(p.Teta)
        }
            
        
        func main() {
          p := Polar{
            R:    1,
            Teta: math.Pi / 2,
          }
        
          c := Cartesian{
            X: 3,
            Y: 4,
          }
        
          fmt.Printf("%g %g\n", p.Distance(), c.Distance())
          fmt.Printf("%g %g\n", c.ImageOnX(), c.ImageOnY())
          fmt.Printf("%g %g\n", p.ImageOnX(), p.ImageOnY())
        }
        
        
        package main
        
        import (
          "fmt"
          "math"
        )
        
        type Point interface {
          Distance() float64
          ImageOnX() float64
          ImageOnY() float64
        }
        
        type Cartesian struct {
          X float64
          Y float64
        }
        
        func (c *Cartesian) Distance() float64 {
          return math.Sqrt(c.X*c.X + c.Y*c.Y)
        }
        
        func (c *Cartesian) ImageOnX() float64 {
          return c.X
        }
        
        func (c *Cartesian) ImageOnY() float64 {
          return c.Y
        }
        
        type Polar struct {
          R    float64
          Teta float64
        }
        
        func (p *Polar) Distance() float64 {
          return p.R
        }
        
        func (p *Polar) ImageOnX() float64 {
          return p.R * math.Cos(p.Teta)
        }
        
        func (p *Polar) ImageOnY() float64 {
          return p.R * math.Sin(p.Teta)
        }
        
        func main() {
          p := Polar{
            R:    1,
            Teta: math.Pi / 2,
          }
        
          c := Cartesian{
            X: 3,
            Y: 4,
          }
        
          fmt.Printf("%g %g\n", p.Distance(), c.Distance())
          fmt.Printf("%g %g\n", c.ImageOnX(), c.ImageOnY())
          fmt.Printf("%g %g\n", p.ImageOnX(), p.ImageOnY())
        }
            
        dep

        Go Packages

        • A package is made up of Go files that live in the same directory and have the same package statement at the beginning.

        greet Package

        
        └── gopherguides
            └── greet
                └── greet.go
          
        
        package greet
        
        import "fmt"
        
        func Hello() {
            fmt.Println("Hello, World!")
        }
          

        main Package

        
        └── gopherguides
            └── example
                └── main.go
          
        
        package main
        
        import "github.com/gopherguides/greet"
        
        func main() {
            greet.Hello()
        }
          
        • Go does not have the concept of public, private, or protected modifiers like other languages do.
        • External visibility is controlled by capitalization.
        • Types, variables, functions, and so on, that start with a capital letter are available, publicly, outside the current package.
        • A symbol that is visible outside its package is considered to be exported.

        Go Modules

        • A module is a collection of packages that are released, versioned, and distributed together. Modules may be downloaded directly from version control repositories or from module proxy servers.
        • A module is identified by a module path, which is declared in a go.mod file, together with information about the module's dependencies.
        • the module golang.org/x/net contains a package in the directory html. That package's path is golang.org/x/net/html.

        Module Path

        • A module path should describe both what the module does and where to find it.
        • The repository root path is the portion of the module path that corresponds to the root directory of the version control repository where the module is developed.
          • Most modules are defined in their repository's root directory, so this is usually the entire path.
          • For example, golang.org/x/net is the repository root path for the module of the same name.
        • If the module is not defined in the repository's root directory, the module subdirectory is the part of the module path that names the directory, not including the major version suffix.
        • If the module is released at major version 2 or higher, the module path must end with a major version suffix like /v2.
          • This may or may not be part of the subdirectory name. For example, the module with path golang.org/x/repo/sub/v2 could be in the /sub or /sub/v2 subdirectory of the repository golang.org/x/repo.

        HTTP Server

        • We want to write our http server to handle request in our way
        • Golang has core and third party libraries for implementing the http server
        • We are going to use echo
        echo

        Install Echo

        
        > go mod init github.com/1995parham-teaching/go-lecture/echo
        > go get github.com/labstack/echo/v4
          

        Routes

        • Build an instance of echo
        • 
          e := echo.New()
              
        • Specify which function shold be called for each route
        • 
          e.POST("/hello", func (c echo.Context) error {
            c.JSON(http.StatusOK, "Hello World")
          })
              

        Goroutines

        • The go statement launches a function call as a goroutine
        • 
          go f()
          go f(x, y, ...)
              
        • A goroutine runs concurrently (but not necessarily in parallel)
        • A goroutine is a thread of control within the program, with its own local variables and stack. Much cheaper to create and schedule than operating system threads.

        A Simple Example

        Function f is launched as 3 different goroutines, all running concurrently:

        
        package main
        
        import (
          "fmt"
          "time"
        )
        
        func f(msg string, delay time.Duration) {
          for {
            fmt.Println(msg)
            time.Sleep(delay)
          }
        }
        
        func main() {
          go f("A--", 300*time.Millisecond)
          go f("-B-", 500*time.Millisecond)
          go f("--C", 1100*time.Millisecond)
          time.Sleep(20 * time.Second)
        }
          

        Communication via channels

        • A channel type specifies a channel value type (and possibly a communication direction):
        • 
          chan int
          chan<- string  // send-only channel
          <-chan T       // receive-only channel
              
        • A channel is a variable of channel type:
        • 
          var ch chan int
          ch := make(chan int)  // declare and initialize with newly made channel
              
        • A channel permits _sending_ and _receiving_ values:
        • 
          ch <- 1   // send value 1 on channel ch
          x = <-ch  // receive a value from channel ch (and assign to x)
            
        • Channel operations synchronize the communicating goroutines.

        Communicating goroutines

        Each goroutine sends its results via channel ch:

        
        func f(msg string, delay time.Duration, ch chan string) {
          for {
            ch <- msg
            time.Sleep(delay)
          }
        }
          

        The main goroutine receives (and prints) all results from the same channel:

        
        func main() {
          ch := make(chan string)
          go f("A--", 300*time.Millisecond, ch)
          go f("-B-", 500*time.Millisecond, ch)
          go f("--C", 1100*time.Millisecond, ch)
        
          for i := 0; i < 100; i++ {
            fmt.Println(i, <-ch)
          }
        }
          

        References 📚

        Fork me on GitHub