Sage-Code Laboratory
index<--

Go Types

In Go languages all variables have a type that can't be changed once established. This makes Go a strongly typed language similar to C++, Rust, Ada and Java. Types are used to verify if expressions or values create the correct data type for specific use-cases at compilation time. This reduce probability of runtime errors.

Go has predefined types, used to declare: constants, variables, parameters and results. Go enable you to declare new data types and composite data types using basic types.

Basic Types

The basic types are the most simple predefined types:

Type Name Keywords Bytes
Boolean bool 1
Integers int, int8, int16, int32, int64; 1-8
Unsigned uint, uint8, uint16, uint32, uint64, uintptr; 1-8
Unsigned byte byte = alias for uint8; 1
Unicode code point rune = alias for int32 4
Fractional numbers float32, float64; 4-8
Complex numbers complex64, complex128; 8-16

Note: The int, uint, and uintptr types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems. When you need an integer value you should use int unless you have a specific reason to use a sized or unsigned integer type.

// file type_num.go
package main

import (
	"fmt"
	"math/cmplx"
)

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
	const f = "%T(%v)\n"
	fmt.Printf(f, ToBe, ToBe)
	fmt.Printf(f, MaxInt, MaxInt)
	fmt.Printf(f, z, z)
}

Factoring:

Variable declarations and import may be "factored" into special blocks, that are enclosed in round brackets (...) not like the usual blocks. This convention is rarely used in other programming languages.

Zero values

Variables declared without an explicit initial value are given their zero value.

The zero value is:

Pointers

A pointer holds the memory address of a variable. Go pointers are using 2 operators: Star "*" in front of the pointer type to define a pointer. Star "*" in front of pointer variable to refer the value stored. Ampersand "&" in front of a variable to get the address of a variable and assign its address to a pointer.

array

Pointer Concept


Example:

//file type_ptr.go
package main

import "fmt"

func main() {
	i, j := 42, 2701

	p := &i     // point to i
	fmt.Println(*p) // read i through the pointer (42)
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i (21)

	p = &j      // point to j
	*p = *p / 37    // divide j through the pointer
	fmt.Println(j)  // see the new value of j (2701/37 = 73)
}

Note: Unlike C, Go has no pointer arithmetic. This is to avoid unsafe pointer values. The pointers are used in Go to send and receive references to variables or to functions.

Composite Structure

In Go a struct is a collection of fields. Each field has a name and a type. Fields are enumerated on different lines. We use struct to define new data types.

//file type_struct.go
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}

Struct fields are accessed using a dot operator "."

//file struc_attr.go
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}

Struct Literals

A struct literal denotes a newly allocated struct value by listing the values of its fields. You can list just a subset of fields by using the "Name:" syntax. The order of named fields is irrelevant. The special prefix & returns a pointer to the struct value.

//file struc_lit.go
package main

import "fmt"

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
	fmt.Println(v1, p, v2, v3)
}

Pointers to structs

Struct fields can be accessed through a struct pointer. To access the field X of a struct when we have the struct pointer p we could write (*p).X. However, that notation is cumbersome, so the language permits us instead to write just p.X, without the explicit de-reference.

//file struc_ptr.go
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 10
	fmt.Println(v) // {10 2}
}

Stringers

A Stringer is a type that can describe itself as a string. The fmt package (and many others) look for this interface to print values. This is an interface defined by the fmt package.

type Stringer interface {
    String() string
}

Example of use:

//file stringer.go
package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"Arthur Dent", 42}
	z := Person{"Zaphod Beeblebrox", 9001}
	fmt.Println(a, z)
}

Type inference

Inside a function, the symbol ":=" is used for assignment. It can be useful in place of a var declaration to assign a value and implicit type from expression evaluation or type literals. This is called "type inference".

//file type_inference.go
package main

import "fmt"
import "reflect"

func main() {
	var i, j int = 1, 2
	k := 3
	c, p, g := true, false, "no!"

	fmt.Println(i, j, k, c, p, g)  
  fmt.Println("typeof(c)=",reflect.TypeOf(c))
  fmt.Println("typeof(g)=",reflect.TypeOf(g))    
}

Note: Outside of a function, every statement begins with a keyword the symbol ":=" is not available.

When declaring a variable without specifying an explicit type (either by using the := syntax or var = expression syntax), the variable's type is inferred from the value on the right hand side. When the right hand side of the declaration is typed, the new variable is of that same type:

var i int
j := i // j is an int

When the right hand side contains a numeric constant, the new variable may be an int, float64, or complex128 depending on the precision of the constant. Most of the time the new type is the right. In the next example we use type inference for i, f and g variables.

package main
import "fmt"

func main() {
   i := 42 // int
   f := 3.142 // float64
   g := 0.867 + 0.5i // complex128
}

Type conversion

Sometimes in Go we need to convert one type to another type. This is called "data coercion". The expression T(v) converts the value v to the type T.

Example: Some numeric conversions:

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

Example: The var keyword is not even required. We can use ":=" symbol to declare and initialize variables. Unlike C the Go assignment between items of different type requires an explicit conversion.

i := 42         //type inference to int
f := float64(i) //explicit conversion
u := uint(f)    //explicit conversion

Type Switch

A type switch is a construct that permits several type assertions in series. A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.

switch v := i.(type) {
  case T:
    // here v has type T
  case S:
    // here v has type S
  default:
    // no match; here v has the same type as i
}

Note:This switch statement tests whether the interface value i holds a value of type T or S. In each of the T and S cases, the variable v will be of type T or S respectively and hold the value held by i. In the default case where there is no match, the variable v is of the same interface type and value as i.

Example: type switch

//file type_switch.go
package main

import "fmt"

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
}

Type Assertions

A type assertion provides access to an interface value's underlying concrete value.

t := i.(T)

This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t. If i does not hold a T, the statement will trigger a panic.

To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded.

t, ok := i.(T)

If i holds a T, then t will be the underlying value and ok will be true. If not, ok will be false and t will be the zero value of type T, and no panic occurs. Note the similarity between this syntax and that of reading from a map.

//file type_assert.go
package main

import "fmt"

func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)

	s, ok := i.(string)
	fmt.Println(s, ok)

    //expect to pring "false"
	f, ok := i.(float64)
	fmt.Println(f, ok)

	//f = i.(float64) // panic
	fmt.Println(f)
}

Read next: Arrays