Struct

Go have built-in types : int , string , float64 , ..etc , there are also composite types : map , array , channel , slice. These types are called composite types because they are composed of other types. For example, a map[string]int64 int64 is actually a collection of values, each stored with a string key for easy retrieval.

If you need custom type , you have to declared it as a struct. In Go , there is no concept of class as java, its only struct.

A simple struct that represents data about a circle :

type Circle struct {             // Declaration of a user-defined type
    x float64
    y float64
    r float64
}

How to create an instances of user-defined types and initialize it ?

a) Declare with a variable

var c Circle                    // create an instance & initialzation.
                                // all local variables x,y,r will be
                                // initialized to 0 by default.

For a struct zero means each of the fields is set to their corresponding zero value (0 for ints, 0.0 for floats, "" for strings, nil for pointers, ?

b) Using new

c := new(Circle)                // c is a pointer

This allocates memory for all the fields, sets each of them to their zero value and returns a pointer.

c) Set values

If we want to give each of the fields a value , we can do write

c := Circle{x: 0, y: 0, r: 8}

Or we can leave off the field names if we know the order they were defined:

c := Circle{0, 0, 5}

Or set it using dot (.)

var c Circle
c.x = 1
c.y = 2
c.r = 9

d) Example

package main

import "fmt"

type Circle struct {    // Declaration of a user-defined type
    o Origin            // Aggregation
    r float64           // built-in type
}

type Origin struct {
    x float64
    y float64
}

func main() {

    var c1 Circle
    fmt.Println("c1 = ",c1)

    c2 := Circle{o: Origin{x:2, y:2}, r:2}
    fmt.Println("c2 = ",c2)

    c3 := Circle{o: Origin{x:2, y:2}}
    fmt.Println("c3 = ",c3)

    c4 := new(Circle) 
    fmt.Println("c4 = ",c4)

    c5 := c4
    fmt.Println("c5 = ",c5.o.x)

    c6 := Circle{o: Origin{x:0, y:1}, r:2}
    fmt.Println("c6 = ",&c6)

    c7 := Circle{o: Origin{6, 7}, r: 8}
    fmt.Println("c7.o.x = ",c7.o.x)
    fmt.Println("c7.o.y = ",c7.o.y)
    fmt.Println("c7.r = ",c7.r)

    c8 := &c7
    fmt.Println("c8.o.x = ",&c7.o.x)
    fmt.Println("c8.o.y = ",c8.o.y)
    fmt.Println("c8.r = ",c8.r)

    c8.o.x = 77            // Structs are mutable.
    fmt.Println(c8.o.x)
}

[output]
c1 =  {{0 0} 0}
c2 =  {{2 2} 2}
c3 =  {{2 2} 0}
c4 =  &{{0 0} 0}
c5 =  0
c6 =  &{{0 1} 2}
c7.o.x =  6
c7.o.y =  7
c7.r =  8
c8.o.x =  0x103222a0
c8.o.y =  7
c8.r =  8
77

Embeding Type

A struct's fields usually represent the has-a relationship.

type Origin struct { 
    x float64
    y float64
}

type Circle struct {
    o Origin            // Circle has-an origin
    r float64
}

But sometimes we would like to use is-a relationship. Go supports this relationship by using an embedded type, it also known as anonymous fields,

package main

import "fmt"

type Courses []string

type Human struct {
    name string
    age int
}

type Student struct {
    Human                   // embedded field, it means Student struct , includes all fields that Human has.
    department string        
    Courses                 // string slice as embedded field
}

func main() {

    moon := Student{Human:Human{"Moon", 23}, department:"Physics"}    

    fmt.Println("moon.name       : ", moon.name)        // access fields
    fmt.Println("moon.age        : ", moon.age)
    fmt.Println("moon.department : ", moon.department)

    moon.age = 45
    fmt.Println("moon.age        : ", moon.age)         // modify age

    moon.department = "Mathematics"                     // modify department
    fmt.Println("moon.department : ", moon.department)

    moon.Human.age = 65                                 // modify age through Human
    fmt.Println("moon.Human.age  : ", moon.Human.age)

    moon.Courses = []string{"Mechanics"}
    fmt.Println("moon.Courses    : ", moon.Courses)

    moon.Courses = append(moon.Courses, "Thermodynamics", "Relativity") 
    fmt.Println("moon.Courses    : ", moon.Courses)
}

[output]
moon.name       :  Moon
moon.age        :  23
moon.department :  Physics
moon.age        :  45
moon.department :  Mathematics
moon.Human.age  :  65
moon.Courses    :  [Mechanics]
moon.Courses    :  [Mechanics Thermodynamics Relativity]

If you used a class-based language, then you are probably wondering why the last example didn't define any methods defined on the structure.

In Go, you may define methods on any concrete type that you define, not just on structures , this is the topic of the section : Methods.

Type conversion

Implicit casting are not allow in Go, because it make very easy introduce subtle bugs into code.

You can convert similar type with possible loss of accuracy, convert dissimilar type will cause compile error.

package main

import "fmt"

func main() {

    f := 123.567
    g := int64(f)                   // We're losing the decimals here
    fmt.Println(f,"/",g)

    h := -3
    i := uint16(h)
    fmt.Println(h,"/",i)

    x :=[]byte("hello")
    fmt.Println(x)
    y := string(x)
    fmt.Println(y)

    type a []string                 // type a
    b:= a{"one", "two"}
    fmt.Println(b)

    c := []string(b)                // convert from custom type to a basic type
    fmt.Println(c)

    d := a([]string{"one", "two"})  // convert from a basic type to a custom type
    fmt.Println(d)

}

[output]
123.567 / 123
-3 / 65533
[104 101 108 108 111]
hello
[one two]
[one two]
[one two]

Type assertion

To convert dissimilar types ( i.e. the empty interface, interface{} or some external unknown source) , we use type assertions.

An interface{} type is a type that could be any Go type. Its like Object in Java.

var a interface{} = "hello"
var b interface{} = 111

You can't use a type conversion here since they are not similar types.

var a interface{} = "hello"
fmt.Println(a)              // [output] hello
c := string(a)              // [output] cannot convert m (type interface {}) to type string: 
                            // need type assertion

You have to use type assertion,

var a interface{} = "hello"
a.(string)                  // we convert the interface{} type using a dot and the required 
                            // type in parentheses.

If the above assertion fails, Go will panic and fail. We write the following code to avoid crash,

c, ok := a.(string)         // if the assertion fails, it won't panic and crash, but will set ok to false.

Sometimes, variables a or b are external package that is not under your control (or interface{}), then you have to check the type before doing the conversion,

package main

import "fmt"

func main() {

    var a interface{} = "hello"
    var b interface{} = 111
    checkAndConvert(a)
    checkAndConvert(b)

}

func checkAndConvert(object interface{}) {

    switch t := object.(type) {

        case string:
            fmt.Println("string")
            // type convertion code here

        case int16, int32, int64:
            fmt.Println("int16,int32, int64")
             // type convertion code here

        default:
            fmt.Println("unknown - ", t)
    }
}

[output]
string
unknown -  111          // it is type int

results matching ""

    No results matching ""