Go Notes

2020-01-06

~All ~Categories

These notes are extracted from the following book: The little Go Book

IntroductionπŸ”—

  • Static Typing
    • Being statically typed means that variables must be of a specific type
  • C like syntax
  • Garbage Collected
  • **Running go code
    • go run hello.go -> compile, run in temp dir then cleans itself up (--work : see temp dir)
    • go build -> compile and generate executable
  • Main function in main package
    • if no main -> build possible but not go run (error)
  • Import
    • strict --> error if unused import
    • local doc with godoc -http=: if needed
  • Variable and declaration
    • Explicit way example:
      var port int // declaration
      port = 3000 // assignment
    • Shorcut with :=
      example:
      port := 3000
      • work with function too instead of 3000
      • only one := per scope for one variable except if other variable as follow:
        secondVar, port := "val1", 4000
  • Functions declarations
    • func <name>( types ) returnType { ... }
      example:
    func log(message int) { ... }
    func add(a int, b int) int { ...}
    func power(name string) (int, bool) {...} // two value return
    
    val, exists := power("goku")
    
    // use _ if want to discard one value (return value not assign):
    _, exists := power("goku")
    

StructureπŸ”—

  • NOT object-oriented
  • Structure definition similar to C
    example:
    type Saiyan struct {
      Name string       
      Power int         
    }                   
    
  • Declaration
    goku := Saiyan{ Name: "Goku", Power: 9000, }                           
    goku := Saiyan{}                                                       
    goku := Saiyan{ "Goku", 9000}  // same order as the fields declarations
    
  • Pointers
    goku := &Saiyan{ Name: "Goku", Power: 9000}
    goku := new Saiyan() // same as &Saiyan{} but without initialisation of fields
    
    ==> new allocate memory
  • Functions on Structure
    func (s *Saiyan) Super(){
      s.Power += 10000       
    }                        
    
  • No constructor -> use of a pattern:
    func NewSaiyan(name string, power int) Saiyan {
      return Saiyan{ name, power }                 
    }                                              
    // no need to return a pointer to Saiyan       
    
  • Field of Structure
    • example:
      type Saiyan struct {
        Name string       
        Power int         
        Father *Saiyan    
      }                   
      
    • initialization:
      gohan:= &Saiyan{  
        Name: "Gohan"   
        Power: 1000,    
        Father: &Saiyan{
          Name: "Goku", 
          Power: 10000, 
          Father: nil   
        },              
      }                 
      

CompositionπŸ”—

Act of including one structure into another
In some language it's called a trait

  • Example in Java:

    public class Person {                                     
      private String name;                                    
      public String getName(){ return this.name; }            
    }                                                         
                                                              
      public class Saiyan{                                    
      private Person person;                                  
      public String getName(){ return this.Person.getName(); }
    }                                                         
    
  • Example in Go

    type Person struct {                                             
      Name string                                                    
    }                                                                
                                                                     
    func (p *Person) Introduce(){ fmt.PrintF("Hi, I'm %s\n", p.Name)}
                                                                     
    type Saiyan struct {                                             
      *Person                                                        
      Power int                                                      
    }                                                                
                                                                     
    // Use                                                           
    goku:= &Saiyan{ Person: &Person{"Goku"}, Power:9000, }           
    goku.Introduce() // work                                         
    

    Bc we didn't give explicit field name to *Person, we can implicitly access the field and functions for the compose type. However Go compiler did give it a field name

    Example :

    goku := &Saiyan{                     
      Person: &Person{"Goku"},           
    }                                    
                                         
    fmt.Println(goku.Name)        // Goku
    fmt.Println(goku.Person.Name) // Goku
    

OverloadingπŸ”—

  • Overloading not supported by GO
    • But we could "overwrite" function of a composed type
      example:
      func (s *Saiyan) Introduce(){            
        fmt.Printf("Hi, I'm %s. Ya!\n", s.Name)
      }                                        
      
      Composed version is always available via s.Person.Introduce()

Pointer versus ValuesπŸ”—

  • Should this be a value, or a pointer to a value ?
  • --> Answer is the same regardless of the following
    • A local variable assignment
    • Field in a structure
    • Return value from a structure
    • Parameters to a function
    • The receiver of a method
  • Secondly if you aren't sure ---> use a pointer
  • --> Passing values is great way to make data immutable
  • --> Consider the cost of creating a copy of large structures

ArrayπŸ”—

  • Fix size ( need a specific size, cannot grow)
    example:
    var scores [10]int 
    scores[0] = 339
    
  • index
    scores[0] β”‚------------β”‚ scores[9]
  • initialize with values:
    scores := [4]int{ 900, 400, 255, 33}
  • len function to get the length of the array
  • range can be used to iterate over it:
    for index, value := range scores {
      // logic
    }   
    
  • ==> Array are efficient but rigids

SlicesπŸ”—

Lightweight structure that wraps and represent a portion of an array
There are few ways to create a slice
A slice is not an array. A slice describes a piece of an array

Interested link: Slices

  • Simple
    slice isn’t declared with a length within the square brackets

    scores []int{ 10, 20, 30, 40} // no size within bracket  
    
  • make
    more than just allocating memory (new)

    scores := make([]int, 5)
    
    • first param : type of the slice
    • second param : size of the slice
    • output: [ 0, 0, 0, 0, 0 ]
    • Here the lenth and the capacity is the same -> 5
     scores := make([]int, 0, 10)    // nil slice nb elements unknown
    
    • first param : type of the slice
    • second param : size of the slice
    • third param : capacity of the slice
    • output: []
    • Here the length is 0 and the capacity is 10
  • Notes

    • length = size of the slice
    • capacity = size of the underlying array
    • append func = could resize the array if original han no more space
      • scores = append(scores,5)
      • capacity *2 each time the original array is full
      • add at the end of the slice not at the begining
  • Is slice a copy ?

    • code:
      scores := []int{1,2,3,4,5}
      slice := scores[2:4]
      slice[0] = 999
      fmt.Println(scores)
      
    • output:
      [1, 2, 999, 4, 5]
      
  • Use copy function
    copy(newSlice, slice)
    The copy function also gets things right when source and destination overlap
    It can be used to shift items around in a single slice.

  • Shorcuts

    slice[X:]     // from X to the end 
    slice[:X]     // from the start to X
    slice[:]      // all element of a slice  
    

MapsπŸ”—

Maps, like slices, are created with the make function
key and value -> get, set and delete values from it
created with make

  • make

    lookup := make(map[string]int)
    lookup["goku"] = 9001
    power, exists := lookup["vegeta"]
      power  -> 0 default value
      exists -> 0, false
    
  • operations

    // len: get number of keys  
    total := len(lookup)
    
    // delete: remove a value based on its key
    delete(lookup, "goku")
     
    // size: Map grow dynamically, however, can supply intial size in make 
    lookup := make(map[string]int, 100)
    //Could help performance if the number of key is known and size defined 
    
  • map as field structure

    type Saiyan struct{
       Name string
       Friends map[string] *Saiyan
    }
    
    goku := &Saiyan{
       Name: "Goku",
       Friends: make(map[string*Saiyan),
    }
    goku.Friends["krillin"]= //load or create krillin
    

    ==> ALTERNATIVE TO INITIALIZATION

    lookup := map[string]int{
       "goku" : 9001,
       "gohan": 2044,
    }
    
    // iteration possible wiht range
    
    for key, value := range lookup { 
       // logic
    }
    
    ITERATION OVER MAP ISN'T ORDERED
    --> RETURN THE KEY IN A RANDOM ORDER
    

VisibilityπŸ”—

  • Simple rule apply for:
    • structure fields
    • functions
  • On the first letter if it's
    • uppercase
      β””--> VISIBLE
    • lowercase
      β””--> NOT VISIBLE

Package managementπŸ”—

  • get third-party libraries
    • go get
  • could get library from github
    example:
    go get github.com/mattn/go-sqlite3
  • go get fetch the remote files and stores them in your workspaces
  • could import this library into a go file
    import (
      "github.com/mattn/go-sqlite3"
    )
    
  • use goop or godep for more advance dependancy manager without some issue of version...

InterfacesπŸ”—

Types taht define a contract but not an implementation

  • example:
    type Logger interface {
      Log(message string)  
    }                      
    
  • use of the interface
    • could be a structure field
      type Server struct {
         logger Logger    
      }                   
      
    • function parameter (or return value)
      func process(logger Logger){
        logger.Log("Hello")       
      }                           
      
    In other language, it have to be explicit when a class implement an interface
    In Go this happens implicitly.

ErrorsπŸ”—

  • No exceptions
  • Return values
  • Could create my own error type
    • requirements to fulfill the contract of the built-in error interface which is:
      type error interface {
        Error() string      
      }                     
      
  • More commonly we can create our own errors by importing the errors package and using it in the New function
    import (                              
      "errors"                            
    )                                     
                                          
    func process (count int) error {      
      if count < 1 {                      
        return errors.New("invalid count")
      }                                   
      ...                                 
      return nil                          
    }                                     
    
  • KEYWORDS
    • panic : throwing exception
    • recover : catch

DeferπŸ”—

  • go has a garbage collection but ressources require that we explicitly release them
    • example when need to Close() file
  • function might have multiple return points
    • Go's solution is the defer keyword
func main(){                              
  file, err := os.Open("a_file_to_read")  
  if err != nil {                         
    fmt.Println(err)                      
    return                                
  }                                       
  defer file.Close()                      
  // read the file                        
}                                         

initialized ifπŸ”—

  • specific if statement
    value can be initilized prior to the condition evaluated
    example:
    if x := 10; count > x {
    }
    
    real example:
    if err := process(); err != nil {
    }
    
    value aren't available outside the if-statement, but inside any else if or else block

Empty interface and conversionsπŸ”—

Go doesn't have any inheritance neither superclass
But it does have empty interface with no methods:

interface {}

Since every type implement all of the empty interface's methods
And since interface are implicitly implemented

  • every type fulfills the contract of the empty interface
  • example with add function
    func add ( a interface{}, b interface{}) interface{} {
     // logic                                             
    }                                                     
    
    to convert an interface variable to an explicit type, use .(TYPE)
    examples:
    func add ( a interface{}, b interface{}) interface{} {
     return a.(int) + b.(int)                             
    }                                                     
    
    if the underlying type is not int ---> error
  • Type switch
    switch a.(type) {            
      case int:                  
         fmt.Printf("val %d\n",a)
      case bool, string:         
         // ...                  
      default:                   
         // ...                  
    }                            
    

Strings and byte arraysπŸ”—

string and byte array are closely related
We can easily convert one to the other
example:

stra := "the spice must flow"
byts := []byte(stra)         
strb := string(byts)         

When using []byte(X) or string(X), you're creating a copy of the data necessary because string are immutable
Strings are made of runes which are unicode code points


Function typeπŸ”—

Functions are first-class types

type Add func(a int, b int) int

Which can then be used anywhere - as a field type, as a parameter, as a return value
example:

type Add func(a int, b int) int                
                                               
func main() {                                  
  fmt.Println(process(func(a int, b int) int {
    return a + b                               
  }))                                          
}                                              
                                               
func process(adder Add) int {                  
  return adder(1,2)                            
}                                              

ConcurrencyπŸ”—

  • Concurrent-friendly language
  • Simple syntax over two powerful mechanisms
    • goroutines
    • channels

GoroutinesπŸ”—

Similar to thread, but it is scheduled by go, not the OS
Code runs in goroutine can cur concurrently with other

Example:

import (                           
  "fmt"                            
  "time"                           
)                                  
                                   
func main() {                      
  fmt.Println("start")             
  go process()                     
  time.Sleep(time.Millisecond * 10)
  fmt.Println("done")              
}                                  
                                   
func process() {                   
  fmt.Println("processing")        
}                                  

Start a go routine by using the keyword "go" followed by the function to exec
We can use an anonymous function for bit of code as following

// anonymous func are not only use by goroutines
go func() {                                     
  fmt.Println("processing")                     
} ()                                            
  • Multiple goroutines will end up running on the same underlying OS thread
    • Often called an M:N threading model bc we have M application threads (goroutine) running on N OS threads
      • -> result is that a goroutine has a fraction of overhead ( a few KB) than OS threads)
      • -> on modern hardware, it's possible to have millions of goroutines
  • the complexity of mapping and scheduling is hidden

IMPORTANT: The process doesn’t wait until all goroutines are finished before exiting
To solve this, we need to coordinate our code

SynchronizationπŸ”—

To help the problem of synchronisation, Go provvide channels

The only concurrent thing you can safely do to a variable is to read from it.
You can have as many readers as you want, but writes need to be synchronized

The most common approach is to use a mutex:
Example:

package main                       
                                   
import ( "fmt"; "time"; "sync")    
                                   
var (                              
  counter = 0                      
  lock sync.Mutex                  
)                                  
                                   
func main() {                      
  for i:=0; i< 20; i++ {           
    go incr()                      
  }                                
  time.Sleep(time.Millisecond * 10)
}                                  
                                   
func incr() {                      
  lock.Lock()                      
  defer lock.Unlock()              
  counter++                        
  fmt.Println(counter)             
}