Built-in types

Types

  • The Zero Value: assigns a default zero value to any variable that is declared but not assigned a value
  • Literals: refers to writing out a number, character, or string. They are of following 3 types
    • Integer Literals: with prefixes (0b for binary, 0o for octal, 0x for hexadecimal) or without prefix (decimal)
      • To make them more readable, underscores are allowed in the middle of the literal
    • Floating-point Literals
    • Rune Literals: represent characters and are surrounded by single quotes.
      • Use a raw string literal (delimited with backquotes (`)) to include backslashes, double quotes, or newlines in the string
  • Booleans
  • Numerical Types: 12 different types (and a few special names) are grouped into 3 categories.
    • Integer types: int8, int16, int32, int64, uint8, uint16, uint32, uint64
    • Special Integer types: byte (alias for uint8), int (alias for int32 in 32-bit CPU and int64 for 64-bit CPU), uint (follows the same rules as 'int'), rune, uintptr.
    • Floating point types: float32, float64
  • Complex Types
    • Types: complex64 (uses float32), complex128 (uses float64)
    • eg: x := complex(2.5, 3.1)
    • Gonum package can be used to write numerical computing applications in Go
  • Strings
    • Go supports Unicode: so you can put any Unicode character into a string.
    • operators: ==, !=, >, >=, <, <=, +
    • Special type: rune (alias for int32)
  • // If you are referring to a character, use the rune type, not the int32 type. They might be the same to the compiler, but you want to use the type that clarifies the intent of your code.

Variable declaration


    /* var keyword with data type */
    var x int = 10

    /* var keyword with implicit data type */
    var x = 10 // guesses x to be of type 'int' by default, but actually has no type of its own (so conversion is easy)

    /* default value */
    var x int // sets default value as 0


    /* multiple declaration inline with same data type */
    var x, y int = 10, 20


    /* multiple declaration inline without data type */
    var x, y = 10, "hi"


    /* multiple declaration block */
    var (
        x int
        y = 10
        z, a = 20, "hi"
    )

    /* shorthand declaration */
    x, y := 10, "hi"
        
// There are some situations within functions where you should avoid :=
  • When initializing a variable to its zero value, use var x int. This makes it clear that the zero value is intended.
  • When assigning an untyped constant or a literal to a variable and the default type for the constant or literal isn't the type you want for the variable, use the long var form with the type specified.
  • Because := allows you to assign to both new and existing variables, it sometimes creates new variables when you think you are reusing existing one
// const declaration is also similar to 'var' declaration. Multiple declarations (inline or in a block) are also allowed. Can be typed or untyped.
// unused variables are not allowed, result in compile-time error. However, unread package-level variables are allowed.

Composite Types

Arrays

collection of elements of similar data types.
  • Declaration
    
        /* array with size specified */
        var x[3]int
    
        /* array with size and values specified */
        var x = [3]int{1,2,3}
    
        /* sparse array, mostly 0 unless specified */
        var x = [12]int{1,5:4,6,10:100,15} // [1,0,0,0,0,4,6,0,0,0,100,15]
    
    
        /* using values to deduce the size */
        var x = [...]int{1,2,3}
    
        /* multi-dimensional array */
        var x[2][3]int
                
  • Important methods
    
        len(x) // size of array 'x'
        cap(x) // same as len(x). is useful for slice
        slicing // slicing an array returns a slice
        copy(y, x[:]) // can be used, but only after converting array to slice
                
  • // Go considers the size of the array to be part of the type of the array (ie an array that's declared to be [3]int a different type from an array that's declared to be [4]int)
    // you can't use a type conversion to convert arrays of different sizes to identical types. Because you can't convert arrays of different sizes into each other, you can't write a function that works with arrays of any size and you can't assign arrays of different sizes to the same variable.
    // Due to these restrictions, don't use arrays unless you know the exact length you need ahead of time.

Slices

similar to array except that the length is not part of the type for a slice.
  • Declaration
    
        /* using values */
        var x = []int{1,2,3}
    
        /* without using values - nil values */
        var x []int
    
        /* empty slice literal */
        var x = []int{}
    
        /* using make: allows to specify type, length, and optionally, the capacity */
        x := make([]int, 5, 10)
                
  • Important methods
    
        len(x) // size of array 'x'
        x = append(x, y) // append y into x and assign it back to x
        cap(x) // returns the current capacity of array 'x'
        y := x[starting_index:ending_index:capacity_of_subslice] // 2 variables sharing same memory
          // capacity is optional and if not provided, subslice's capacity is set to the capacity of the original slice
        copy (y, x) // copies elements of 'x' into 'y' and returns the length of the source slice
          // both source and destination can be sliced eg. num =  copy(x[:3], x[1:])
                
  • // as slice has no type, default value for slice element is nil
    // it isn't comparable. using '==' or '!=' with slice results in a compile-time error.
    • Alternatively, DeepEqual() of 'reflect' package can be used to compare slices (among almost anything)
    // The rules as of Go 1.14 are to double the size of the slice when the capacity is less than 1,024 and then grow by at least 25% afterward.

Strings

  • Properties
    • Under the cover, Go uses a sequence of bytes to represent a string.
    • are immutable
    • bracket syntax can be used to read a substring
    • slicing is allowed
  • Type conversion
    • single rune or byte can be converted to a string: string(x)
    • string() won't work with int. eg string(65) gives 'A', and not '65'
    • string to slices
      
          var y []byte = []byte(x)
          var z []rune = []rune(x)
                      

Maps

for association of one value to another.
  • Declaration
    
        /* set to its zero value (ie nil) */
        var x map[string]int
    
        /* shorthand notation to create a map var by assigning it a map literal */
        x := map[string]int{}
    
        /* map with known size // although this size can grow if required */
        x := make(map[string]int, 10)
                
  • Important Methods
    
        len(x) // number of key-value pairs in the map
        x, ok := m["hi"] // The comma ok idiom
          // If ok is true, the key is present in the map. If ok is false, the key is not present.
        delete(m, "hello") // deletes key-value pair with key "hello" in map "m"
                
  • // maps are not comparable. although you can check if they are equal to nil
    // keys of a map need to be of comparable types, ie you cannot use a slice of a map as the key for a map.
    // doesn't follow ordering.

Structs

  • Declaration
    
        type person struct {
          name string
          age int
        }
    
        /* var declaration */
        var fred person
    
        /* := declaration with empty struct literal */
        bob := person{}
    
        /* declaration with non-empty struct literal */
        julia := person{
            "Julia",
            40
        }
    
        /* declaration with map-style literal */
        beth := person{
            name: "beth",
            age: 50
        }
                
  • Anonymous structs: 2 common usages
    • marshalling and unmarshalling data (conversion to and from external data (like JSON or protocol buffers))
    • writing tests
    
        pet := struct {
            name string
            kind string
        }{
            name: "Tommy",
            kind: "dog",
        }
                
  • Struct conversion: One struct can be converted into another if all the 3 conditions are met:
    • the number of fields are same.
    • name of all the fields are same.
    • data type of all the fields are same.

Naming convention

  • Though unicodes are allowed, we should avoid them
  • Go prefers camel case over snake case
  • Go uses the case of the first letter in the name of a package-level declaration to determine if the item is accessible outside the package.
    • upper case means exported, and lower case means not exported
  • The smaller the scope for a variable, the shorter the name that's used for it
  • When naming variables and constants in the package block, use more descriptive names. The type should still be excluded from the name, but since the scope is wider, you need a more complete name to make it clear what the value represents.

Shadowing Variables

  • A shadowing variable is a variable that has the same name as a variable in a containing block. For as long as the shadowing variable exists, you cannot access a shadowed variable.
  • This is one of the reasons to avoid using :=, because it reinitialises as well as creates a new variable.
    • When using :=, make sure that you don't have any variables from an outer scope on the lefthand side, unless you intend to shadow them.
  • Add shadow detection to your build process by installing the shadow linter on your machine ($ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest)
  • The built-in types (like int and string), constants (like true and false), and functions (like make or close) or nil aren't included in the list of keywords. Instead Go considers these predeclared identifiers and defines them in the universe block. Because these names are declared in the universe block, it means that they can be shadowed in other scopes.