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