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.
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)
}
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.
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.
Pointer Concept
//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.
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)
}
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)
}
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}
}
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)
}
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
}
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
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)
}
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