Monday, April 12, 2021

Golang - Type System

Go Type System



Because Go is a static type system, type checking occurs at build time.

Dynamic type systems, on the other hand, such as JavsScript, can postpone type checking until runtime.

Refer to this article.

The Go Type System is designed to catch bugs as early as possible.

Before we go into types in Go, we should first learn about bits and bytes.


Bit


A bit can be either 0 or 1.

We can use bits to encode information, for example, 0 is 'false' and 1 is 'true'.

The number of bits in 1 bit is 2 (2 to the power of 1).


    fmt.Printf("%b\n"0)
    fmt.Printf("%b\n"1)


Result:
      
0
      1


We can utilize more bits to represent more information.

The amount of information that may be obtained from two bits is four (2 to the power of two).


    fmt.Printf("%02b\n"0)
    fmt.Printf("%02b\n"1)
    fmt.Printf("%02b\n"2)
    fmt.Printf("%02b\n"3)


Result:

    00
    01
    10
    11


More Details about "%02b" format from the example above:

* b: base 2
* 0: starting with 0 if needed
* 2: show 2 digits


Byte


A byte is made up of 8 bits.

Bytes, as opposed to bits, represent the smallest and most meaningful piece of information.

1 byte = 8 bits = 256 possible values (2 to the power of 8)

The term 'byte' refers to a numeric data type.


    var b1 byte
    b1 = 0
    fmt.Printf("%08b - %v\n", b1, b1)

    var b2 byte
    b2 = 255
    fmt.Printf("%08b - %v\n", b2, b2)


Result:

    00000000 - 0
    11111111  - 255



Predeclared Types



Predeclared types are built-in types that can be used anywhere.

* bool      =>      true or false      =>     1 byte (the minimum object is byte)


* int         =>     based on your pc, it could be int32 or int64 (modern)

* int8       =>     -128 to 127

* int16     =>     -32768 to 32767

* int32     =>     -2 billions to +2 billions

* int64     =>     -9 quintillions to +9 quintillions


* uint       =>     based on your pc, it could be uint32 or uint64 (modern)

*uint8      =>     0 to 255

* uint16   =>     0 to 65535

* uint32   =>     0 to 4 billions

* uint64   =>     0 to 18 quintillions


* float32 

* float64 


* string     =>     []byte


The number after type means bit-size, which define the range of value a type can represent.


Overflow



Consider the following example if the value exceeds the maximum.


    // range: 0 to 255
    var counter uint8 = 255
    fmt.Println("old: ", counter)

// overflow!
    counter++
    fmt.Println("new: ", counter)


Result:

    old:  255
    new:  0


If there is an overflow, the value is reset to its minimum.

Because it is an unsigned integer, it will be reset to 0.

Let's see if it's a signed integer.


    var counter int8 = math.MaxInt8
    fmt.Println("old: ", counter)

    counter++
    fmt.Println("new: ", counter)


Result:

    old:  127
    new:  -128



You can check 'integer overflow' to get more info.


Defined Types (Named Types)



Consider the following example.

It includes methods for converting between Celsius and Fahrenheit.

exp/mian.go
package main

import "fmt"

func convertCelsiusToFahrenheit(celsis float64float64 {
     return (celsis * 9 / 5) + 32
}

func convertFahrenheitToCelsius(fahrenheit float64float64 {
     return (fahrenheit - 32) * 5 / 9
}

func main() {
     var celsius float64 = 100
     var fahrenheit float64 = 212

     fmt.Printf("celsis: %f, ferenheit: %f\n", celsius, convertCelsiusToFahrenheit(celsius))
     fmt.Printf("celsis: %f, ferenheit: %f\n"convertFahrenheitToCelsius(fahrenheit), fahrenheit)
}


However, because both methods take float64 input, it is a nightmare to identify this error if the wrong function is invoked.

Is there any way we can increase 'type safety'?

To avoid it, Go offers a defined type (also known as a named type). Refer to this doc.

A defined type can only be created from an already existing type.

Consider the Duration type in a time package as an example.


    type Duration int64


Duration is a specific type.

And specified types inherit attributes from real-structured types.

The underlying type is int64.

int64 is a valid data type. It is organized.

    operations: +-*/%

    representation: -1, 0, 1

    size: 8 bytes

However, a defined type and its underlying type are distinct kinds.
As a result, they cannot be used in the same expression without being converted.


    var totalSeconds time.Duration = 1000000000
    var secondToAdd int64 = 1000000000

    fmt.Println(totalSeconds + secondToAdd)


Error:

   invalid operation: totalSeconds + secondToAdd (mismatched types time.Duration and int64)


If two types share the same underlying type, they can be converted to each other.

 
    var totalSeconds time.Duration = 1000000000
    var secondToAdd int64 = 1000000000

    fmt.Println(totalSeconds + time.Duration(secondToAdd))


Result:

   2s


Then we can modify our software as shown below.

temperature/temperature.go

package temperature

// Celsius ...
type Celsius float64

// Fahrenheit ...
type Fahrenheit float64

// ConvertCelsiusToFahrenheit ...
func ConvertCelsiusToFahrenheit(celsis Celsius) float64 {
     return float64(celsis)*9/5 + 32
}

// ConvertFahrenheitToCelsius ...
func ConvertFahrenheitToCelsius(fahrenheit Fahrenheit) float64 {
     return (float64(fahrenheit) - 32) * 5 / 9
}


temperature/cmd/main.go

package main

import (
     "fcheng/temperature"
    "fmt"
)

func main() {

     var celsius temperature.Celsius = 100
    var fahrenheit temperature.Fahrenheit = 212

     fmt.Printf("celsis: %f, ferenheit: %f\n", celsius, 
temperature.ConvertCelsiusToFahrenheit(celsius))
    fmt.Printf("celsis: %f, ferenheit: %f\n"
temperature.ConvertFahrenheitToCelsius(fahrenheit), fahrenheit)
}



Other advantages of using defined types include:

* Improved readability

    We can tell that 'Duration' refers to time rather than guessing what int64 is.

* Increase type safety

    As in the last temperature example.

* Make available customized ways


Aliased Types



It is the same type as before, but with a different name.
It enhances readability and eliminates the need to transform the type.

For example, byte and uint8 are identical types with different names.
In addition, for greater readability, we recommend byte over uint8.

The following code executes without error.


    var byteValue byte
    var uint8Value uint8

    uint8Value = byteValue
    fmt.Println(uint8Value)


No comments:

Post a Comment