Golang keyword -- type definition

reference resources Go keyword -- type In addition to the definition of type, the original text also introduces several other applications of type. This article only talks about type definition.

type can be used in the following ways:
1. Define structure
2. Define the interface
3. Type definition
4. Type alias
5. Type query

1, Type definition - excerpt from page 69 of Go language Bible

To illustrate the type declaration, we define different temperature units as different types:

// Package tempconv performs Celsius and Fahrenheit temperature computations.
package tempconv

import "fmt"

type Celsius float64 // Centigrade temperature
type Fahrenheit float64 // fahrenheit

const (
    AbsoluteZeroC Celsius = -273.15 // Absolute zero
    FreezingC Celsius = 0 // Freezing point temperature
    BoilingC Celsius = 100 // Boiling water temperature
)

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }

In this package, we declare two types: Celsius and Fahrenheit, which correspond to different temperature units. Although they have the same underlying type float64, they are different data types, so they can not be compared with each other or mixed in an expression operation. Deliberately distinguishing types can avoid some errors caused by inadvertently using different units of temperature mixed calculation; Therefore, an explicit transformation operation in the form of Celsius(t) or Fahrenheit(t) is required to convert float64 to the corresponding type. Celsius(t) and Fahrenheit(t) are type conversion operations, and they are not function calls. Type conversion does not change the values themselves, but changes their semantics. On the other hand, CToF and FToC functions convert the temperature in different temperature units, and they will return different values.

For each type T, there is a corresponding type conversion operation T(x) to convert x to t. This transformation operation is allowed only when the underlying basic types of the two types are the same, or both are pointer types pointing to the same underlying structure. These transformations only change the type without affecting the value itself. If x is a value that can be assigned to type T, then x must also be converted to type T, but this is generally not necessary.

The transformation between numeric types is also allowed, and it is also possible to convert between strings and some specific types of slice. We will see such examples in the next chapter. Such transformations may change the performance of the value. For example, converting a floating-point number to an integer will discard the decimal part, and converting a string to a slice of [] byte type will copy a copy of the string data. In any case, there will be no conversion failure error at runtime.

The underlying data type determines the internal structure and expression, and also determines whether it can support built-in operators like the underlying type. This means that the arithmetic operation behavior of Celsius and Fahrenheit types is the same as that of the underlying float64 type, as we expect.

fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C
boilingF := CToF(BoilingC)
fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F
fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch

The comparison operators = = and < can also be used to compare a variable of a named type with another variable of the same type, or the value of an unnamed type with the same underlying type. However, if two values have different types, they cannot be compared directly:

var c Celsius
var f Fahrenheit
fmt.Println(c == 0) // "true"
fmt.Println(f >= 0) // "true"
fmt.Println(c == f) // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"!

Pay attention to the last sentence. Although it seems like a function call, Celsius(f) is a type conversion operation. It doesn't change the value, just the type of the value. The reason the test is true is because both c and g are zero.

2, Type definition makes the code more concise

The type defined by type definition is different from the original type, so the new type variable cannot be assigned to the original type variable unless forced type conversion is used. Let's take a look at a sample code. Define a new type according to the string type. The name of the new type is name:

type name string

Why use type definitions? Type definition can create new types based on the original types. In some cases, it can make the code more concise, such as the following example code:

package main

import (
    "fmt"
)

// Defines a function type that receives a string type parameter
type handle func(str string)

// exec function, receiving parameters of handle type
func exec(f handle) {
    f("hello")
}

func main() {
    // Define a function type variable, which receives a string type parameter
    var p = func(str string) {
        fmt.Println("first", str)
    }
    exec(p)

    // Anonymous functions are passed directly to the exec function as parameters
    exec(func(str string) {
        fmt.Println("second", str)
    })
}

The output information is:

first hello
second hello

The above example is a simple application of type definition. If you do not use type definition, how should you write this code to realize the functions in the above example?

// exec function, receiving parameters of handle type
func exec(f func(str string)) {
    f("hello")
}

The parameter type in the exec function needs to be replaced with func(str string). It doesn't seem complicated at first glance, but what if exec receives a function variable that needs 5 parameters? Does it feel that the parameter list will be very long.

func exec(f func(str string, str2 string, num int, money float64, flag bool)) {
    f("hello")
}

It can be seen from the above code that the readability of the parameter list of exec function becomes poor. Next, let's take a look at how to use type definitions to achieve this function:

package main

import (
    "fmt"
)

// Define a function type that requires five parameters
type handle func(str string, str2 string, num int, money float64, flag bool)

// exec function, receiving parameters of handle type
func exec(f handle) {
    f("hello", "world", 10, 11.23, true)
}

func demo(str string, str2 string, num int, money float64, flag bool) {
    fmt.Println(str, str2, num, money, flag)
}

func main() {
    exec(demo)
}

III Explain time in Go language in detail Duration type

In the Time package, a type named Duration and some auxiliary constants are defined:

type Duration int64

const (
    Nanosecond Duration = 1
    Microsecond = 1000 * Nanosecond
    Millisecond = 1000 * Microsecond
    Second = 1000 * Millisecond
    Minute = 60 * Second
    Hour = 60 * Minute
)

Then comes the test code:

func Test() {
    var waitFiveHundredMillisections int64 = 500

    startingTime := time.Now().UTC()
    time.Sleep(10 * time.Millisecond)
    endingTime := time.Now().UTC()

    var duration time.Duration = endingTime.Sub(startingTime)
    var durationAsInt64 = int64(duration)

    if durationAsInt64 >= waitFiveHundredMillisections {
        fmt.Printf("Time Elapsed : Wait[%d] Duration[%d]\n", 
        waitFiveHundredMillisections, durationAsInt64)
    } else {
        fmt.Printf("Time DID NOT Elapsed : Wait[%d] Duration[%d]\n",
        waitFiveHundredMillisections, durationAsInt64)
    }
}

After running this test code, I get the following output. From the output content, the 500 millisecond time I defined has been used up, but how is it possible.

Time Elapsed : Wait[500] Duration[10724798]

From the above knowledge, it is easy to see the problem. The basic unit of time in the Duration type is Nanosecond, so when I convert an object of Duration type representing 10 milliseconds to int64 type, I actually get 10000000.

Test it like this

func Test() {
    var waitFiveHundredMillisections time.Duration = 500 * time.Millisecond

    startingTime := time.Now().UTC()
    time.Sleep(600 * time.Millisecond)
    endingTime := time.Now().UTC()

    var duration time.Duration = endingTime.Sub(startingTime)

    if duration >= waitFiveHundredMillisections {
        fmt.Printf("Wait %v\nNative [%v]\nMilliseconds [%d]\nSeconds [%.3f]\n", 
        waitFiveHundredMillisections, duration, duration.Nanoseconds()/1e6, duration.Seconds())
    }
}

In fact, the Duration type has some convenient type conversion functions, which can convert the Duration type into the built-in type int64 or float64 of Go language, as follows:

func Test() {
    var duration_Seconds time.Duration = (1250 * 10) * time.Millisecond
    var duration_Minute time.Duration = 2 * time.Minute

    var float64_Seconds float64 = duration_Seconds.Seconds()
    var float64_Minutes float64 = duration_Minute.Minutes()

    fmt.Printf("Seconds [%.3f]\nMinutes [%.2f]\n", float64_Seconds, float64_Minutes)
}

I also quickly noticed that in the time conversion function, there is no function to convert the millisecond value. Using the Seconds and Minutes functions, I get the following output:

Seconds [12.500]
Minutes [2.00]

But I need to convert the millisecond value. Why is there no conversion of millisecond value in the package? Because the designers of the Go language wanted me to have more choices than just converting millisecond values to a separate built-in type. In the following code, I convert the millisecond value to int64 type and float64 type:

func Test() {
    var duration_Milliseconds time.Duration = 500 * time.Millisecond

    var castToInt64 int64 = duration_Milliseconds.Nanoseconds() / 1e6
    var castToFloat64 float64 = duration_Milliseconds.Seconds() * 1e3
    fmt.Printf("Duration [%v]\ncastToInt64 [%d]\ncastToFloat64 [%.0f]\n",
    duration_Milliseconds, castToInt64, castToFloat64)
}

I divide the nanosecond value by 1e6 to get the millisecond value represented by int64. Multiply the second value by 1e3 to get the millisecond value represented by float64. The output of the above code is as follows:

Duration [500ms]
castToInt64 [500]
castToFloat64 [500]

Refer to the source code in the built-in package time, which is easy to understand:

func (d Duration) Seconds() float64 {
    sec := d / Second
    nsec := d % Second
    return float64(sec) + float64(nsec)/1e9
}

4, Don't confuse type definition and type alias

The difference between type alias and type definition is that the use of type alias requires an assignment symbol (=) between the alias and the original type; The type defined by type alias is equivalent to the original type, while the type defined by type is a new type.
stay What's new in Golang 1.9 - Type Aliases:

1. Original design intention
The original intention of type alias is to solve the problem of type transfer between packages during code refactoring (Reference)  Codebase Refactoring (with help from Go) ).

Consider the following scenarios:

There is a package called p1 in the project, which contains a structure T1. As the project progresses, T1 becomes larger and larger. We hope to extract T1 and put it into a separate package p2 through code refactoring without affecting other existing codes. In this case, the function of the previous go language can not well meet such needs. The introduction of type aliases can provide good support for such requirements.

First, we can extract T1 related codes into package p2:

package p2
 
type T1 struct {
...
}
func (*T1) SomeFunc() {
...
}

Then put the alias of T1 in p1

package p1
import "p2"

type T1 = p2.T1

Through this operation, we can extract types into new packages without affecting other existing code. Existing code can still use T1 by importing p1. Instead of switching to p2 immediately, you can migrate step by step. Such refactoring occurs not only in the above scenarios, but also in the following scenarios:

  • Optimized naming: such as io. In earlier Go versions Change the byte buffer to bytes Buffer.
  • Reduce dependency size: for example, IO EOF was once placed in os EOF. In order to use EOF, you must import the entire os package.
  • Solve the problem of circular dependency.

reference resources Go language | Go 1.9 new features Type Alias detailed explanation
The main purpose of the type alias feature is to be used for the compatibility of defined types when moving between packages. For example, we have an exported type flysnow Org / lib / T1, now we need to migrate to another package, such as flysnow Org / lib2 / T1. If we do this without type alias, it will lead to the code of other third parties referring to the old package path, which must be modified uniformly, otherwise it cannot be used.

With type alias, we can migrate the implementation of type T1 to lib2. At the same time, we define an alias of T1 under lib2 under the original lib, so that the third-party reference can be used normally without modification. It only needs to be compatible for a period of time, and then completely remove the type compatibility in the old package, so that we can gradually reconstruct our code, Not one size fits all.

//package:flysnow.org/lib
type T1=lib2.T1

Keywords: Go Back-end

Added by tequilacat on Thu, 10 Feb 2022 02:54:49 +0200