go-basic

overview

Go is a statically typed, compiled programming language designed at Google, Go is syntactically similar to C, but with memory safety, garbage collection, structural typing, concurrency.

Go is influenced by C, but with an emphasis on greater simplicity and safety.

  • A syntax and environment adopting patterns more common in dynamic languages:

    • Optional concise variable declaration and initialization through type inference (x := 0 instead of int x = 0; or var x = 0;).
    • Fast compilation.
    • Remote package management (go get) and online package documentation.
  • Distinctive approaches to particular problems:

    • Built-in concurrency primitives: light-weight processes (goroutines), channels, and the select statement.
    • An interface system in place of virtual inheritance, and type embedding instead of non-virtual inheritance.
    • A toolchain that, by default, produces statically linked native binaries without external dependencies.

Syntax
Go’s syntax includes changes from C aimed at keeping code concise and readable. A combined declaration/initialization operator was introduced that allows the programmer to write i := 3 or s := “Hello, world!”, without specifying the types of variables used. This contrasts with C's int i = 3; and const char *s = "Hello, world!"; Semicolons(;) still terminate statements,but are implicit when the end of a line occurs. Methods may return multiple values, and returning a result, err pair is the conventional way a method indicates an error to its caller in Go. Go adds literal syntaxes for initializing struct parameters by name and for initializing maps and slices. As an alternative to C’s three-statement for loop, Go’s range expressions allow concise iteration over arrays, slices, strings, maps, and channels.


Built-in Types

  • bool
  • string
  • int int8 int16 int32 int64
  • uint uint8 uint16 uint32 uint64 uintptr
  • byte // alias for uint8, used as char like c,'a' + 1 is valid it's 'b'
  • rune // alias for int32 represents a Unicode code point
  • float32 float64
  • complex64 complex128
  • pointer

The int, uint types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems, it depends on arch, different like C, int, uint are 32 bits(4 bytes) even on 64-bit machine


Custom type

1
2
3
4
5
6
// status and bool are two different types.
type status bool

/* Some type alias declarations */
// boolean and bool denote the same type.
type boolean = bool

NOT Supported

  • inheritance
  • assertions
  • pointer arithmetic
  • implicit type conversions
  • NO ~x but ^x in Go for integer.

Tools
The main Go distribution includes tools for building, testing, and analyzing code:

  • go build, which builds Go binaries using only information in the source files themselves, no separate makefiles
  • go test, for unit testing and microbenchmarks
  • go fmt, for formatting code
  • go get, for retrieving and installing remote packages
  • go vet, a static analyzer looking for potential errors in code
  • go run, a shortcut for building and executing code, but not save binary to disk.
  • godoc, for displaying documentation or serving it via HTTP

An ecosystem of third-party tools adds to the standard distribution, such as gocode, which enables code autocompletion in many text editors, goimports, which automatically adds/removes package imports as needed, anderrcheck, which detects code that might unintentionally ignore errors.


single quote vs double quote They are different

To declare either byte or rune we use single quote. While declaring byte we have to specify the type. If we do not specify the type, then the default type is meant as a rune for 'a'. A single quote will allow only one character.

1
2
3
4
5
6
7
func test() {
var ch byte = 'a'// must declare with type byte, otherwise it's rune !!!
ch := byte('a')

rc := 'a' /* default it's a rune */
rs := "a" /* it's string */
}

NO -> for pointer type like what we did in C, but works as C like &a, *p, **p, *p=

1
2
3
4
5
6
7
8
9
10
var a int = 12
var p *int = &a // pointer type and get object address
*p = 10

type Student struct {
x int
}

var p *Student = &Student{x: 1}
p.x = 12 // Go automatically convert it to (*p).x = 12 !!!

string, slice, map behave like pointer, but when assigning and passing as parameter, array, struct are different.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import "fmt"

func test() {
var s1 = "hello"
s2 := s1 // both s2 and s1 points to same memory

sc1 := []int{1, 2, 3, 4}
sc2 := sc1 // both sc1 and sc2 points to same underlaying array

m1 := map[string]int{
"a": 1,
"b": 2,
}

m2 := m1 // both m1 and m2 points to same memory


/*-----------------------------------------*/
var a1 = [5]int{1, 2, 3, 4, 5}
a2 := a1 // a2 is a copy of a1, different memory!!!

var st1 = struct {
x, y int
}{1, 2}

st2 := st1 //st2 is a copy of st1
}

for/range when loop array, slice, map, it’s copy of element, hence if change on that element, make sure use s[i] if element is not pointer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Person struct {
name string
}

func main() {
// slice of Person
ps := []Person{
{name: "a"},
}

for _, p := range ps {
p.name = "b" // p is copy of each element
}

// nothing changed
fmt.Println(ps)

for i := 0; i < len(ps); i++ {
ps[i].name = "b"
}
fmt.Println(ps)
}

Go Wiki Overview

naming convention

Go is to use MixedCaps or mixedCaps rather than underscores to write multiword names.

Files

  • Go follows a convention where source files are all lower case with underscore separating multiple words, client_log.go
  • Compound file names are separated with _
  • Files with the suffix _test.go are only compiled and run by the go test tool.

Functions and Methods

  • Use camel case, exported functions should start with uppercase
  • If a name consists of multiple words, each word after the first should be capitalized like this: empName, EmpAddress, etc.
  • function names are case-sensitive (car, Car and CAR are three different variables).

Constants

  • Constant should be capitalized(camel case like Exported named). WorldStdEncoding

Variables

  • shouldn’t include the name of your type in the name of your variable’s name, tetMap
  • Generally, use relatively simple (short) name(lower case), camel case(NOT _ underscore for multiple worlds) long var.
    • user to u
    • userID to uid
    • serverListener
    • lpcfg
  • If variable type is bool, its name should start with Has, Is, Can or Allow, etc.
  • Single letter represents index: i, j, k

struct interface

  • Name of struct or interface should be capitalized and camel case type BJSchool struct{}
  • method of interface and struct should be capitalized(exported) and camel case type School interface{ Name() string }
  • field of struct should be low letter starts camel case if not exported, otherwise uppercase type School struct { regStudent int }

import package

  • package name should be lowercase, no camel case. like import xxx/testhello

Non-exported struct fields can be accessed only in the same package, can not be accessed by other package.

printing

FMT Print cheat-sheet

Package fmt implements formatted I/O with functions analogous to C’s printf and scanf. The format ‘verbs’ are derived from C’s but are simpler.

In Golang we can use Printf with a special format code. This determines how a string or integer is formatted. Println does not require a format string.

  • Printf: Must provide the format and support explicit argument indexes, no auto newline.
  • Println: No special format support, auto newline for each output, auto space between arguments, just use it’s default.
  • Print: Print does not insert a newline after each call and no auto space between arguments, it just writes the data to the console with no trailing newline, except this, same as Println.

Above three prints to console while Sxx returns the formated result.

  • Sprintf: Must provide the format and support explicit argument indexes, No auto newline.
  • Sprintln: No special format support, auto newline, just use it’s default.
  • Sprint: Print does not insert a newline after each call, it just writes the data to the console with no trailing newline, except this, same as Sprintln.

Above three prints returns the formated result, while Fxx writes data to io.Writer

  • Fprintf: Must provide the format and support explicit argument indexes, No auto newline.
  • Fprintln: No special format support, auto newline, just use it’s default.
  • Fprint: Print does not insert a newline after each call, it just writes the data to the file with no trailing newline, except this, same as Fprintln.

explicit argument index

1
2
3
4
a := 10
b := 20
fmt.Printf("%v %v\n", a, b) // 10 20
fmt.Printf("%[2]v %[1]v\n", a, b) // 20 10 argument index

default format of each type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%c    print for byte and rune only
%q a single-quoted character literal safely escaped with Go syntax, used for byte,rune and string
%v the value in a default format
[when printing struct], the plus flag (%+v) adds field names--->suggested way.
[when printing struct], the plus flag (%#v) adds field names and struct definition
%T a Go-syntax representation of the [type of the value]

The default format for %v is:

bool: %t
int, int8, rune, byte: %d
uint, uint8 etc.: %d
float32, complex64, etc: %g
string: %s
chan: %p
pointer: %p

print in multiple lines

1
2
3
4
5
s := `hello
world` // auto enter!!! without \n needed

s := "hello\n" +
"world"

%q vs %c vs %s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
)

func main() {
c := byte('a')
fmt.Printf("%q\n", c)
fmt.Printf("%c\n", c)

s := "hello"
fmt.Printf("%q\n", s)
fmt.Printf("%s\n", s)
}

'a'
a
"hello"
hello

More details refer to fmt package, like C format, but more simpler to use.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import "fmt"

func print_demo() {
a := 10
b := 20
s := "hello"

fmt.Print(a, "\n")
fmt.Print("Print: no auto space added between arg ", a, s) // no space bettween each argument

fmt.Println()
fmt.Println(a)
fmt.Println("Println: each arg is separated by space automatically", a, s)//each argument is separated by space
fmt.Println("Println: not support special format:", a, "not cool") // auto new line

fmt.Printf("Printf: support special format: %[2]v %[1]v\n", a, s) //argument must be at most right part, mannually new line


result := fmt.Sprintf("Sprintf: support special format: %[2]v %[1]v", a, b) // use argument index
fmt.Println(result)

/* print array, slice, struct */
slice := []int{999, 99, 9}
array := [3]string{"a", "b", "c"}
fmt.Println(slice)
fmt.Printf("%v\n", array)

type Person struct {
name string
id int
}

var p1 Person = Person{"jason", 1}
fmt.Println(p1)
fmt.Printf("%#v\n", p1)
fmt.Printf("%+v\n", p1)
fmt.Printf("%v\n", p1)

// s = hello
fmt.Printf("%v %q\n", s, s)

var s byte = 65
fmt.Printf("%v %q\n", s, s)
}

print_demo()
10
Print: no auto space added between arg 10hello
10
Println: each arg is separated by space automatically 10 hello
Println: not support special format: 10 not cool
Printf: support special format: hello 10
Sprintf: support special format: 20 10
[999 99 9]
[a b c]
{jason 1}
struct { 𒀸name string; 𒀸id int }{𒀸name:"jason", 𒀸id:1}
{𒀸name:jason 𒀸id:1}
{jason 1}
hello "hello"
65 'A'

builtin API

func append(slice []Type, elems ...Type) []Type

The append built-in function appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not sufficient, a new underlying array will be allocated

func cap(v Type) int

  1. Array: the number of elements in v (same as len(v)).
    2.Pointer to array: the number of elements in *v (same as len(v)).
    3.Slice: the maximum length the slice can reach when resliced, may different with len(slice)
    4.if v is nil, cap(v) is zero.
    5.Channel: the channel buffer capacity, in units of elements;

func close(c chan<- Type)

The close built-in function closes a channel not a file, which must be either bidirectional or send-only. It should be executed only by the sender, never the receiver, and has the effect of shutting down the channel after the last sent value is received. After the last value has been received from a closed channel c, any receive from c will succeed without blocking, returning the zero value for the channel element

func copy(dst, src []Type) int shadow copy(only the top level is copied)

The copy built-in function copies elements from a source slice into a destination slice. (As a special case, it also will copy bytes from a string to a slice of bytes.) The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum of len(src) and len(dst).

func delete(m map[Type]Type1, key Type)

delete element specified by key from a map

func len(v Type) int

1.Array: the number of elements in v.
2.Pointer to array: the number of elements in *v (even if v is nil).
3.Slice, or map: the number of elements in v; if v is nil, len(v) is zero.
4.String: the number of bytes in v.
5.Channel: the number of elements queued (unread) in the channel buffer;
6.if v is nil, len(v) is zero.

func make(t Type, size ...IntegerType) Type

Can be used only for Slice, Map, Channel

Slice: The size specifies the length. The capacity of the slice is
equal to its length. A second integer argument may be provided to
specify a different capacity; it must be no smaller than the
length. For example, make([]int, 0, 10) allocates an underlying array
of size 10 and returns a slice of length 0 and capacity 10 that is
backed by this underlying array.

Map: An empty map is allocated with enough space to hold the
specified number of elements. The size may be omitted, in which case
a small starting size is allocated.  

Channel: The channel's buffer is initialized with the specified
buffer capacity. If zero, or the size is omitted, the channel is
unbuffered. 

# for slice, can pass two parameters
s1 = make([]int, 4)   // len=4 and cap = 4
s1 = make([]int, 0, 4)// len=0 and cap = 4

# for map, no one needed
m1 = make(map[string]int)

# for channel, can pass one parameter
c1 = make(chan int)     // buffer size 0(unbuffered)
c2 = make(chan int, 10) // buffer size 10
                                                                                      

func new(Type) *Type

The new built-in function allocates memory. The first argument is a type, not a value, and the value returned is a pointer to a newly allocated zero value of that type. most of time, we does not use it at all.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import "fmt"

func builtin_demo() {
s1 := []int{1, 2, 3}
s2 := make([]int, 4)
fmt.Println(s1, s2)

copy(s2, s1)
fmt.Println(s1, s2)

p := new(int)
*p = 12
fmt.Println(p, *p)
}

builtin_demo()
[1 2 3] [0 0 0 0]
[1 2 3] [1 2 3 0]
0xc0009190d0 12

constants

untyped value has default type
An untyped value means the type of the value has not been confirmed yet. For most untyped values, each of them has one default type, All literal constants (unnamed constants) are untyped values. most untyped values are literal constants and named constants.


The default type of a literal constant is determined by its literal form.

  • The default type of a string literal is string.
  • The default type of a boolean literal is bool.
  • The default type of an integer literal is int.
  • The default type of a rune literal is rune (a.k.a., int32).
  • The default type of a floating-point literal is float64.
  • If a literal contains an imaginary part, then its default type is complex128

constant type

unnamed constant(literal constant)

1
2
12
"hello"

named constant with untyped value

1
const MAX = 12

named constant with typed value

1
const MAX int8 = 12

type deduction(type inference)

Go supports type deduction. In other words, in many circumstances, programmers don’t need to explicitly specify the types of some values in code. Go compilers will deduce the types for these values by context.

In Go code, if a place needs a value of a certain type and an untyped value (often a constant) is representable as a value of the certain type, then the untyped value can be used in the place. Go compilers will view the untyped value as a typed value of the certain type. it can be viewed as implicit conversions.

constant declaration way

= not := for constant declaration

untyped named constant

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

// Declare two individual constants. Yes,
// non-ASCII letters can be used in identifiers.
const π = 3.1416
const Pi = π // <=> const Pi = 3.1416

// Declare multiple constants in a group called constant specification can be different types.
const (
No = !Yes
Yes = true
MaxDegrees = 360
Unit = "radian"
)

func main() {
// Declare multiple constants in one line. can be different types!!!
const TwoPi, HalfPi, Unit2 = π * 2, π * 0.5, "degree"
}

typed named constant

1
2
3
4
5
6
7
8
9
10
11
12
13
const X float32 = 3.14

const (
A, B int64 = -3, 5 // A and B are same type
Y float32 = 2.718
)

// If a basic value literal is bound to a typed constant,
// the basic value literal must be representable as a value of the type of the constant.

// error: 256 overflows uint8
const a uint8 = 256
const MaxUint uint = (1 << 64) - 1 // error on 32-bit as (1 << 64) - 1 is not representable as 32-bit values

Autocomplete in constant declarations

In a group-style constant declaration, except the first constant specification, other constant specifications can be incomplete. An incomplete constant specification doesn’t contain the = symbol. Compilers will autocomplete the incomplete lines for us by copying the missing part from the first preceding complete constant specification.

1
2
3
4
5
6
7
8
const (
X float32 = 3.14
Y // here must be one identifier, Y has same like X.

A, B = "Go", "language"
C, _
// In the above line, the blank identifier is required to be present.!!!
)

iota is a special value controlled by compiler, its value is reset to 0 for each const keyword the first constant line of group, and increased by 1 for each appearance before next const keyword.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

func main() {
const (
k = 3 // now, iota == 0

m float32 = iota + .5 // m float32 = 1 + .5
n // n float32 = 2 + .5

p = 9 // now, iota == 3
q = iota * 2 // q = 4 * 2
_ // _ = 5 * 2
r // r = 6 * 2
s, t = iota, iota // s, t = 7, 7 iota has the same vale on the same line
u, v // u, v = 8, 8
_, w // _, w = 9, 9
)

const x = iota // x = 0
const (
y = iota // y = 0
z // z = 1
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const (
a = 1 << iota // a == 1
b = 1 << iota // b == 2
c = 1 << iota // c == 4
)

// Same as above

const (
a = 1 << iota // a == 1
b
c
)

const (
Failed = iota - 1 // == -1
Unknown // == 0
Succeeded // == 1
)

NOTE

  • Constants are declared like variables, but with the const keyword.
  • Constants can be character, string, boolean, or numeric values.
  • Constants can NOT be declared using the := syntax.
  • This is no enum in GO, use const instead
  • Constants can be declared both at package level and function bodies.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import "fmt"

// like enum
const (
Sunday = iota // Sunday == 0
Monday // Monday == 1
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays
)

const Pi = 3.14

func constanDemo() {
fmt.Printf("day %v\n", Sunday)
const World = "world"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")

const Truth = true
fmt.Println("Go rules?", Truth)
const Ch byte = 'a'
fmt.Println("Char", Ch)
fmt.Printf("Char %c", Ch)
}
constanDemo()
day 0
Hello world
Happy 3.14 Day
Go rules? true
Char 97
Char a

variable

All variables are typed values. When declaring a variable, there must be sufficient information provided for compilers to deduce the type of the variable

There are two basic variable declaration forms, the standard one and the short one. The short form can only be used to declare local variables


NOTE

  • var is not needed for declaration like in struct, function parameter, function return value
  • var is a must when declare global variable, optional for local variable.
  • All variables are addressable and all constants are unaddressable
  • Go doesn’t support assignment chain, like this a = b = 123.

Suggestion

  • constant, use const statement not var statement
  • global variable, use var statement
  • local variable, but no need explicit initialization(default value), use var statement
  • local variable, needs initialization, use := statement
  • with assignment at declaration, always use short way.

As Go is compiled language, hence we must know the type of each variable at declaration either by explicit or implicit(assigned value), the type of variable is determined at declaration, can’t be change during running!!!

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

standard way

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var lang, website string = "Go", "https://golang.org"
var compiled, dynamic bool = true, false
var announceYear int = 2009

// var of annymous struct type
// g is a variable of struct type who has one field.
var g struct {
name string
}

// var of function type
var g func() string
var g int

var c, d = 1, "hello"
var next = 12

var (
lang, bornYear, compiled = "Go", 2007, true
announceAt, releaseAt int = 2009, 2012
createdBy, website string
)

var g // error as no way to know the type at declaration
var a, b int
a = b = 123 // syntax error, Go doesn't support assignment chain

short way

Short variable declarations can only be used to declare local variables.

There are several differences between short and standard variable declarations.

  • In the short declaration form, the var keyword and variable types must be omitted.
  • The assignment sign must be := instead of =.
  • In the short variable declaration, old variables and new variables can mix at the left of :=. But there must be at least one new variable at the left.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

func main() {
// Both lang and year are newly declared.
lang, year := "Go language", 2007

// Only createdBy is a new declared variable.
// The year variable has already been
// declared before, so here its value is just
// modified, or we can say it is redeclared.
year, createdBy := 2009, "Google Research"

if true {
// both month, year are new within this scope
month, year:=12, 2006
}

fmt.Println(year) // it's still 2009!!!

// This is a pure assignment.
lang, year = "Go", 2012

nextYear := year // same type as year
}

NOTE := declare all new variables left, not part of it

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func main() {
a := []string{"a", "b", "c"}
i := 0

// for scope
for i, e := range a {
// i is a new var with scope for!!!
fmt.Println(i, e)
}

fmt.Println("=================")
fmt.Println(i)
}

# go run test.go
0 a
1 b
2 c
=================
0

default value of each type(without explicit initialization)

Variables declared without an explicit initial value are given their zero value, you can access var with zero directly, one except is for map, you can NOT modify nil map!!

1
2
3
4
5
6
7
8
9
10
11
var m map[string]int // nil map
m["a"] = 12 // panic!!!

m := map[string]int{} // empty map
m["a"] = 12 // it's ok

var s []int // nil slice
s = append(s, 1)// it's ok!!!

s := []int{} // empty slice
s = append(s, 1) // ok as well

zero value for each type

  • 0 for numeric types,
  • false for the boolean type
  • “” (the empty string) for string.
  • nil for pointer
  • nil for function type
  • nil, but len(map) == 0
  • nil, but len(slice) == 0
  • zero value for all fields for struct instance

variable initialization order

When a variable depends on another variable b, b should be defined beforehand, else program won’t compile. Go follows this rule inside functions. but it’s not true for global variable.

1
2
3
4
5
6
7
8
package main
var a int = b // it's ok
var b int = 12

func main() {
var c int = d // error
var d int = 12
}

pointer

The type *T is a pointer to a T type.

1
2
3
4
5
6
7
8
9
var p *int

//The & operator generates a pointer to its operand.

i := 42
p = &i

fmt.Println(*p) // read i through the pointer p
*p = 21 // set i through the pointer p

scope

A variable or a named constant declared in an inner code block will shadow the variables and constants declared with the same name in outer code blocks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

const y = 789
var x int = 123

func main() {
// The x variable shadows the above declared
// package-level variable x.
var x = true

// A nested code block.
{
// Here, the left x and y are both
// new declared variable. The right
// ones are declared in outer blocks.
x, y := x, y

// In this code block, the just new
// declared x and y shadow the outer
// declared same-name identifiers.
x, z := !x, y/10 // only z is new declared
y /= 100
println(x, y, z) // false 7 78
}
println(x) // true
println(z) // error: z is undefined.
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import "fmt"

var ga, gb = 1, "global b" // implicit type
var gc, gd int // must explicit type as no assignment

func var_demo(){
gc = 10
gd = 20

la, lb := "local a", 30 // implicit type

// lb = "hi" error as lb has type int!!!

var lc string // must explicit type as no assignment
lc = "local c"
fmt.Println(ga, gb, gc, gd, la, lb, lc)
}

var_demo()
1 global b 10 20 local a 30 local c

types

Get Max value of integer, use math lib which provides Max of Int8, Int16, Int32, Int64, Int and unsigned version as well.

1
2
3
//do it by yourself

const MaxUint = ^uint(0)

conversion

Identical types, no need for conversion

Two types are identical if their underlying type literals are structurally equivalent; that is, they have the same literal structure and corresponding components have identical types. In detail:

  • Two array types are identical if they have identical element types and the same array length.

  • Two slice types are identical if they have identical element types.

  • Two struct types are identical if they have the same sequence of fields, and if corresponding fields have the same names, and identical types, and identical tags. Non-exported field names from different packages are always different.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type A_ID int
    type A struct {
    id A_ID
    }

    type B_ID int
    type B struct {
    id B_ID
    }
    // A and B are different type!!

    1
    2
    3
    4
    5
    6
    7
    8
    type A struct {
    name string
    }

    type B struct {
    name string
    }
    // A and B are same type
  • Two pointer types are identical if they have identical base types.

  • Two function types are identical if they have the same number of parameters and result values, corresponding parameter and result types are identical, and either both functions are variadic or neither is. Parameter and result names are not required to match.

  • Two interface types are identical if they have the same set of methods with the same names and identical function types. Non-exported method names from different packages are always different. The order of the methods is irrelevant.

  • Two map types are identical if they have identical key and element types.

  • Two channel types are identical if they have identical element types and the same direction.

Different types
Unlike in C, in Go assignment between different types(if possible) requires an explicit conversion, there are two ways to use explicit type conversion, other different types can NOT be converted.


  • number: int() uint()
  • number<—>string: strconv.Atoi("12"), strconv.Itoa(12) Or fmt.Sprintf("%v",12)

you can Convert int to string in this way, the result may be not what you want j := string(97), j is "a" not "97"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import "fmt"
import "strconv"

func testp(x *int) {
//x is a pointer
*x = 20
}

func test(x int) {
// x is copy, value passed
x = 10
}

func typeDemo() {
var a int = 12
// convert int to uint
var b uint = uint(a)

s := strconv.Itoa(10)
c, _ := strconv.Atoi("20")

fmt.Println(a, b, s, c)


testp(&a)
fmt.Println(a)
test(a)
fmt.Println(a)
}

typeDemo()
12 12 10 20
20
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type A struct {
name string
}

type B struct {
name string
}

func main() {
a := A{name: "jack"}
// B and A are same type, can convert to each other!!!
var b B = B(a)

fmt.Println(b.name)
}
main()
jack
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type A_ID int
type A struct {
id A_ID
}

type B_ID int
type B struct {
id B_ID
}

func main() {
a := A{id: 100}
var b B = B(a)

fmt.Println(b.id)
}
main()
100

string

A string is a struct that has a length and a pointer to a byte array. When you pass a string to another function, it copies the length and the pointer. As a consequence, the new copied string points to the same underlying data.

each element of string is a byte like s[0], string is immutable, you can NOT modify it in place


Create a string

  • var s string
  • var s = “hello”
  • s := “hello”
  • var s = strconv.Itoa(12): Int to string: “12”
  • string(97): 97 is “a”, so “a” is printed

Ops

  • s[0], s[0] is byte type!!!
  • last element s[len(s)-1], s[-1] NOT supported
  • s[0:3]
  • s += “extend it”
  • string([]byte{56,57}) // convert byte slice to string, new memory is created!!!
  • string(slice)
  • support s1 == s2
  • for _, c:= range s {} c is rune type!!!

  • string itself does not have method like Find, Match while strings library provides ops for it
  • Can NOT convert array to string but slice is allowed.
    1
    2
    3
    ar :=[2]byte{56,57}
    fmt.Println(string(ar)) //error
    fmt.Println(string(ar[:])) // copy array to slice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import "fmt"

func testp(s *string) { // string pointers points to argument
fmt.Println(s, *s)
}

func test(s string) { // no copy just like pointer, points to same memory
fmt.Println(s)
}

func stringDemo(){
s := "hello"
b := s // b and s points to same memory, but can NOT modified.

fmt.Printf("%c %s %v %v\n", s[0], s[0:3], &s, &b)
testp(&b)
test(b)

sc := []byte("boy") // convert string to byte slice

s = string(sc) // convert byte slice to a string
fmt.Println(s)

s = `hello
world`
// multiple lines
fmt.Println(s)

s1 := "hello"
s2 := "hello"

if s1 == s2 { // compare two strings
fmt.Println("string equal")
}

// default format for rune is %d!!!
fmt.Printf("%T, %c, %v\n",s1[1], s1[1], s1[1])
fmt.Println(len(s1))
}

stringDemo()
h hel 0xc0004df3e0 0xc0004df400
0xc0004df400 hello
hello
boy
hello
world
string equal
uint8, e, 101
5

array and slice

Like C an array has an unique type, initialize with {}, arrays cannot be resized, size is fixed at initialization, index from 0 like C.

  • Arrays are values. Assigning one array to another copies all its elements.
  • In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.
  • The size of an array is part of its type. The types [10]int and [20]int are distinct type.

slice and array conversion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//---------------array to slice(no need to define slice firstly)--------------------                             
var arr1 = [3]int{1, 2, 3}

// ss is slice
ss := arr1[:] // points to same memory!!!
ss[0] = 4 // arr1 changed as well.

// copy element from array to slice(new memory)
s := make([]int, 2)
copy(s[:], arr1[:]) // s and arr1 points to different memory
//copy(s[:], arr1[:1]), s does not extend even if arr1 is larger
fmt.Println(s)

//---------------slice to array(need to define array firstly)--------------------

sc := []int{1, 2, 3}
var arr2 [3]int
copy(arr2[:], sc[:]) // copy all

//copy(arr2[:], sc[:2]) // copy part
fmt.Println(arr2)

Create an array

  • var arr [3]int: all zero
  • arr := [3]int{1, 2, 3}
  • arr := [3]int{}: all zero
  • var arr [3]interface{} // array of any type
  • arr := [3]interface{}{}
  • arr := [3]interface{}{“a”, 2, 1} array of any type

array of maps

1
2
3
4
5
6
7
8
9
10
11
12
// each element of the array is a map.
var ts = [2]map[string]int{{}, {}}
ts[0]["a"] = 1

//OR

var ts [2]map[string]int
// must create empty map
ts[0] = map[string]int{}
ts[1] = map[string]int{}

ts[0]["a"] = 1

Ops

  • arr[0]
  • last element: arr[len(arr) - 1]
  • arr[0:3] not include arr[3]
  • for i, v := range arr {fmt.Println(v)}
  • arr = append(arr, ‘a’, ‘b’) //arr may point to new memory!!!
  • arr = append(arr, another_arr…) // link two arrays

slice

An array has a fixed size must be provided at declaration. A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array. In practice, slices are much more common than arrays.

A slice is formed by specifying two indexes, a low and high bound(not included), separated by a colon:
a[low:high]

you may omit the high or low bounds to use their defaults instead. The default is zero for the low bound and the length of the slice for the high bound.

These slice expressions are equivalent:

1
2
a[0:10] == a[:10]
a[0:] == [:]

A slice does not store any data, it just describes a section of an underlying array, Slices are like references to arrays

Changing the elements of a slice modifies the corresponding elements of its underlying array.

The underlying array is dynamic and can be enlarged(or reduced to smaller one) to a new larger array(may larger than real elements) if append to a slice, hence a slice has both a length and a capacity.

The length of a slice is the number of elements it contains.

The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.

The length and capacity of a slice s can be obtained using the expressions len(s) and cap(s).


Create a slice

  • var sc []int
  • sc := []int{1, 2}, nsc = sc[:] // nsc and sc point to same underlaying memory
  • sc := []byte(“hello”) // byte slice from string
  • sc := make([]int, 0, 5)
  • arr := [10]int{}; sc := arr[1:5]; sc := arr[:], sc and arr points to same memory
  • sc := []interface{}{}
  • sc := []interface{}{“a”, 1} slice of any type.
  • func test(sp *[] int) pointer to slice!!!

Create a slice of map, each slice element is a map

1
2
3
4
5
6
7
8
9
10
11
// slice with 0 element
var ts = []map[string]int{}
//OR
ts := make([]map[string]int, 0)

// slice with 1 map
ts = append(ts, map[string]int{})
ts[0]["a"] = 1
// slice with 2 maps
ts = append(ts, map[string]int{})
ts[1]["b"] = 1

Ops

  • sc[0]
  • last element: sc[len(sc) - 1]
  • sc[0:3]
  • for i, v := range sc {fmt.Println(v)}
  • sc = append(sc, 12) // sc may point to new memory!!!
  • sc = append(sc, 12, 13) // sc may point to new memory!!!
  • sc = append(sc, another_sc…) // sc may point to new memory!!!
  • inset element at index sc = append(sc[:index+1], orig[sc:]...) orig[index] = value
  • remove element at index sc = append(sc[:index], sc[index+1:]...)

Note

  • For append(), If the backing array of s is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.
  • New element is put at the end of len, may overwrite underlaying array if it’s part of it
  • empty slice is nil with len == 0 but len(s)==0, s may be not nil
    1
    2
    s2 := make([]int, 0, 4)
    len(s2) == 0 // but s2 is not nil!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func test() {
s := []int{1, 2, 3, 4, 5}
b := s[0:3] // here b and s shared the same underlaying

b = append(b, 6) // append at b[3] as len of b is 3!!!
fmt.Println(s)
fmt.Println(b) // b and s still shares the same underlaying array, hence 4 is replaced with 6

b = append(b, 7)
fmt.Println(s) // b and s still shares the same underlaying array, hence 5 is replaced with 7
fmt.Println(b)

b = append(b, 8) // a new larger underlay array is created and returned
fmt.Println(s) // s still points to old underlaying array, b points to newly allocated array
fmt.Println(b)

b = append(b, 9)
fmt.Println(s)
fmt.Println(b)
}

[1 2 3 6 5]
[1 2 3 6]

[1 2 3 6 7]
[1 2 3 6 7]

[1 2 3 6 7]
[1 2 3 6 7 8]

[1 2 3 6 7]
[1 2 3 6 7 8 9]

how slice cap change
every slice has an underlying array, an array may be shared among several slices. If the new slice’s length will exceed the array’s capacity, a new array will be created for the new slice. Usually new capacity will be two times old capacity

cap(s), count elements from the beginning of slice to the end of underlay memory.

make([]byte, 5)

s = s[2:4], cap(s) == 3

Go only supports move start of underlaying array, but the end, the first two is dropped, memory is recycled!!!

s = s[:3], cap(s) == 5!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main                                                                    

import "fmt"

func main() {
b := make([]byte, 10)
fmt.Println(cap(b)) //10
s1 := b[:5]
fmt.Println(cap(s1)) //10
s2 := s1[1:]
fmt.Println(cap(s2)) //9
s3 := b[5:]
fmt.Println(cap(s3)) //5
}

NOTE

  • It’s ok to loop a nil slice, same thing for map as well
    1
    2
    3
    4
    var n []int                                                                 
    for _, i := range n {
    fmt.Printf("%d\n", i)// nothing print as n is nil
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import "fmt"

func test(sc [] int) {
// slice is refernece to array,
// sc passed with its header(passed value)
// which has a pointer to array memory and length field, append here will not be seen by caller as length is value passed.
sc[0] = 100
}

func arraySliceDemo() {
var arr = [5]int{1,2,3,4,5}
//arr := [5]int{2, 2, 3, 4, 5}
fmt.Println(arr, arr[len(arr)-1])

// slice of array
var s []int = arr[0:4]
fmt.Println(s)
s[0] =10

b := s // b and s shared the same underlaying array

// the underlaying array is changed as well
fmt.Println(arr, s, cap(s), len(s))

s = []int{10, 20, 30, 40, 50}
fmt.Println(s, cap(s), len(s))

// append supports add more value to slice
s = append(s, 60, 70) // an new array is allocated and slice points to that
fmt.Println(s, cap(s), len(s))

s = make([]int, 5) // len(a)=5, cap(a) = 5
fmt.Println(s, cap(s), len(s))

s = make([]int, 0, 5) // len(a)=0, cap(a)=5
fmt.Println(s, cap(s), len(s))

// anonymous struct here.
s := []struct {
a int
b int
}{
{1, 2},
{3, 4},
}
fmt.Println(s)

type Ver struct {
a int
b int
}
s := []Ver{{1,2}, {3,4}}
fmt.Println(s)

// loop each element of a slice or an array
for i, v := range s {
fmt.Println(i, v) // v is copy of each element(copy of pointer of object)!!!
}

for _, v := range s {
// just the value, discard the index
fmt.Println(v)
}

// another way to loop slice or array
for i :=0; i < len(s); i++ {
fmt.Println(s[i])
}

// array
var arr = [5]int{1, 2, 3, 4, 5}
narr := arr // not like slice, narr is a copy of arr!!!
arr[0] = 6 // narr is unchanged, different with C language.

fmt.Println(arr, narr)

// slice
var arr = []int{1,2}
test(arr)
fmt.Println(arr)

// an array of any type
an := [2]interface{}{1, "hi"}

var ab [2]interface{} // interface{} is type!!!
ab[0] = 2
ab[1] = "two"
fmt.Println(an, ab)

sc := []int{1, 2}
nsc := append(sc[:], []int{3, 4}...)// 3,4 append to new slice sc[:]!!!
fmt.Println(sc, nsc)// sc is not changed!!!
}
arraySliceDemo()
[1 2 3 4 5] 5
[1 2 3 4]
[10 2 3 4 5] [10 2 3 4] 5 4
[10 20 30 40 50] 5 5
[10 20 30 40 50 60 70] 10 7
[0 0 0 0 0] 5 5
[] 5 0
[{1 2} {3 4}]
[{1 2} {3 4}]
0 {1 2}
1 {3 4}
{1 2}
{3 4}
{1 2}
{3 4}
[6 2 3 4 5] [1 2 3 4 5]
[100 2]
[1 hi] [2 two]
[1 2] [1 2 3 4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import "fmt"

func slice_demo1() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)

// Slice the slice to give it zero length.
s = s[:0]
printSlice(s)

// Extend its length. why it's extend!!???
s = s[:4]
printSlice(s)
}

func slice_demo2() {
s := []int{2, 3, 5, 7, 11, 13}

s = s[1:4]
printSlice(s)

s = s[:2]
printSlice(s)

s = s[1:]
printSlice(s)
}

func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

slice_demo1()
fmt.Println("demo")
slice_demo2()
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
demo
len=3 cap=5 [3 5 7]
len=2 cap=5 [3 5]
len=1 cap=4 [5]

map(dict)

Key of map can be of any type for which the equality operator is defined, such as integers, floating point and complex numbers, strings, pointers, interfaces (as long as the dynamic type supports equality), structs and arrays. Slices cannot be used as map keys, because equality is not defined on them, value can by any type like int, string, slice, function etc


Create a map

  • var m map[string]int: map[string]int sits at right side when assigning values
  • m := map[string]int{} // empty map
  • m := map[string]int{“a”: 1, “b”: 2}
  • m := make(map[string]int)
  • m := map[string]interface{}{}
  • m := map[string]interface{}{“a”: 1, “b”: “b”} key must be quoted when it’s string literal
  • m := map[string]func(i string){} map of function object.

create map whose value is a slice

1
2
3
4
5
6
7
8
9
10
var m = map[string][]int{} // empty map
//OR
var m = make(map[string][]int)

m["a"] = []int{} // create a new slice
m["a"] = append(m["a"], 1)
m["a"] = append(m["a"], 2)

m["b"] = []int{3, 4}
}

create map whose value is func object

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func hello(m string) {
fmt.Println(m)
}
func main() {
m := map[string]func(i string){}
m["a"] = hello
m["a"]("hello")
}

Ops

  • m[“c”]= 3
  • elem, ok = m[key]
  • delete(m, “c”): It’s safe to do even if the key is absent from the map

you CAN NOT assign value for nil map, you must create it first!!!

1
2
3
4
5
var mt map[string]int // nil map
mt["cool"] = 12 ERROR!!!

mt := map[string]int{} // map is created
mt := make(map[string]int) // map is created

But it’s ok to loop a nil map

1
2
3
4
var n map[string]int                                                             
for _, i := range n {
fmt.Printf("%d\n", i)// nothing print as n is nil
}

Note

  • Access map by map[key] NOT map.key
  • The key of map must be same type, but the value can be any type when use interface{} as value type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import "fmt"

func test(m map[string]int) {
m["f"] = 100
// m[12] = "hi" // ERROR!!!
}

func mapDemo() {
// literal maps
// {} is initializer

var m1 = map[string]int { // {"a":1, "b":2 } is initializer same line, , can be omitted
"a": 1,
"b": 2, // each element must end with ,!!!
}

//m3 and m1 points to same memory, like pointer!!!
m3 := m1

// change map
test(m3)

m1["e"] = 15

m2 := map[string]int {
"c": 3,
"d": 4, // each element must end with even the last one if at different line!!!
}

m2 := map[string]int {"c": 3, "d": 4} // the last , can be omitted if at same line with }
m2["e"] = 13

delete(m2, "e")
fmt.Println(m1, m3, m2)

//Mutating Maps

// var mt map[string]int
// mt["cool"] = 12 ERROR!!!

m := make(map[string]int) // map without element

m["Answer"] = 42
fmt.Println("The value:", m["Answer"])

m["Answer"] = 48
fmt.Println("The value:", m["Answer"])

delete(m, "Answer")
fmt.Println("The value:", m["Answer"])

v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
m["a"] = 1
m["b"] = 2
fmt.Println(m)
// loop a map
for k, v := range m {
fmt.Println(k, v)
}

var mn = map[string]interface{}{}
mn["a"] = 12
mn['b'] = "hi"
fmt.Println(mn, mn["a"], mn['b'])
}

mapDemo()
map[a:1 b:2 e:15 f:100] map[a:1 b:2 e:15 f:100] map[c:3 d:4]
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
map[a:1 b:2]
a 1
b 2
map[a:12 b:hi] 12 hi

struct

Struct fields can be accessed by struct instance or through a struct pointer which uses . NOT -> like what did in C

1
2
3
4
type Vertex struct {
x, y int //NOT var x int!!!
//as it's lowercase, non-exported field!!!
}

Create a struct instance

  • var st Vertex
  • st := Vertex{1, 2} // unamed assignment, must provide all values!!!
  • st := &Vertex{x: 1} // named assignment, can provide part of values!!!
  • st := Vertex{x: 1, y: 2} NOT “x” or “y” when use named index!!!
  • st := Vertex{}

Ops

  • st.x = 10
  • p := &st
  • p.x = 10 not p->x

Note

  • Access field of struct by st.field_name not st[“field_name”] like what we do for map
  • pointer still uses p.field_name to access filed which is converted to (*p).field by Go automatically
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import "fmt"

type Vertex struct {
x, y, z int //no var
//as it's lowercase, non-exported field!!!
}

var (
v1 = Vertex{1, 2, 3} // has type Vertex
v2 = Vertex{y: 1} // x:0 is implicit, set named field, error for unamed setting Vertex{1}
v3 = Vertex{} // x:0 and y:0 and z:0 if unset fields!!!
p = &Vertex{1, 2, 3} // has type *Vertex
)

func structDemo() {
var pt *Vertex
// as pt is pointer, must use &
pt = &Vertex{1, 3, 4,// , is needed if } is at newline!!
}
fmt.Println(v1, p, v2, v3, *pt)

// array of struct
var pa []*Vertex
pa = []*Vertex {// type of each element
{4, 5, 6}, // not & even it's pointer type!!!
}
fmt.Println(v1, p, v2, v3, *pt, *pa[0])
}

structDemo()
{1 2 3} &{1 2 3} {0 1 0} {0 0 0} {1 3 4}
{1 2 3} &{1 2 3} {0 1 0} {0 0 0} {1 3 4} {4 5 6}

function

Always remember Go is compiled language, hence, each parameter and return value must have a type, NO default value supported for parameter func test(x=12, y), Unsupported named parameter call like test(y=12, x=13).

function can return any number of results

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func add(x int, y int) int {
return x + y
}

// omit type if use the same type
func add(x, y int) int {
return x + y
}

// named return value, like declared a var z at top of the function
func add(x, y int) (z int) {
// use z directly
z = x + y
return // no explicit, return named value, but return directive is a must!!!
}

// return two values
func add(x, y int) (int, int) {
z = x + y
return z, x
}

defer

A defer statement defers the execution of a function until the surrounding function returns.

The deferred call’s arguments are evaluated immediately, but the function call is not executed until the surrounding function returns. deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

package main

import "fmt"

func Get() int {
i := 0
defer func() {
i = i + 10
}()

return i
}

func main() {
fmt.Println(Get())
}
..
0

Note

  • defer GetPerson().GetName() only the last call GetName() is deferred, GetPerson() is called immediately!!!
  • deferred call’s arguments are evaluated immediately
  • deferred function should no return, if wants return value, use channel, if deferred function has return value, it’s not captured!!!
  • deferred function executes after return statement!!!
  • As go is compile, hence deferred may not be pushed to stack, if code not reach it!!!.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func add(a, b int) chan int {
resCh := make(chan int)
go func() {
resCh <- a + b
}()

return resCh
}

func main() {
resCh := add(1, 2)

res := <-resCh
fmt.Println("1 + 2 =", res)
}
main()
1 + 2 = 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func test(cool bool) {
if cool {
return
}

defer func() {
fmt.Printf("defer %v\n", cool)
}()
}

func main() {
test(true) // no defer executes as cool is true!!!
test(false)
}

main()
defer false

function object

Function is an object, so it can be used as argument or return value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import "fmt"
func add(x, y int) int {
return x + y
}
// unnamed function
var hadd = func(x, y int) int {
return x + y
}
// func(int, int) int: is (function) type!
func test(fn func(int, int) int) int {
return fn(1, 2)
}

fmt.Println(test(add))
fmt.Println(test(hadd))

function closure

function closure is a function that returns another function, but you can NOT define a function in another function like this

1
2
3
4
5
6
7
func test() {
func embed() { // Compile Error!!!
}

hembed := func() { // OK as hembed is a variable which points to unnamed function
}
}

closure Return unnamed function

1
2
3
4
5
6
7
func adder() func(int) int { // return value is a function
sum := 0
return func(x int) int { // unnamed function
sum += x // always access var defined at its wrapper which is like a static variable!!!
return sum
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

// hello is function wrapper which takes wrapped function as parameter
// return the wapper function which is used by caller
// inside the wrapper, add internal logical, then call wrapped function
func hello(fn func(string) error) func(string) {
return func(name string) {
fmt.Print("wrapper: ")
fn(name)
}
}

func greet(name string) error {
fmt.Println("hello", name)
return nil
}

func main() {
wp := hello(greet)
wp("tom")
}

main()
wrapper: hello tom

variadic function(dynamic parameters)

In Go, a function that can accept a dynamic number of arguments is called a Variadic function. Below is the syntax for variadic function. Three dots are used as a prefix before type.

dynamic parameters with same type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// same type, ... is closer to type, it's a new type ...int
// numbers behaves like a slice, packed parameters
func add(numbers ...int) int { // must be the last parameter of a function!!!
sum := 0
for _, num := range numbers { // numbers is like a slice
sum += num
}
return sum
}

add()
add(1,2)
add(1,2,3,4)

var numbers := []int{2,3,5}
add(numbers...) // call with slice, expand slice, same as add(2, 3, 5), unpacked parameter

dynamic parameters for different types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func test() {
handle(1, "abc")
handle("abc", "xyz", 3)
handle(1, 2, 3, 4)
}

// interface{} for any type as it has no method defined
// ...interface{} behaves like a new type

func handle(params ...interface{}) {
fmt.Println("Handle func called with parameters:")
for _, param := range params {
fmt.Printf("%v\n", param) // print the value of special type
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import "fmt"

// function as an argument
func test(fn func(int, int) int) int {
return fn(1, 2)
}

// function as return value
func adder() func(int) int {
sum := 0
return func(x int) int { // unnamed function
sum += x
return sum
}
}

func funcDemo() {
// defer function
defer fmt.Println("boy")
defer fmt.Println("girl")
fmt.Println("hello")

// declare a function object
hadd := func(x, y int) int {
return x + y
}
fmt.Println(test(hadd))

// closure
f1 := adder() // f1 has its own copy of sum, all call f1 shares the same sum.
fmt.Println(f1(1))
fmt.Println(f1(1))

f2 := adder() // f2 has its own copy of sum
fmt.Println(f2(1))
}

funcDemo()
hello
3
1
2
1
girl
boy

function type

Think function signature(without name) as a type, you can declare variable, parameter, new type based on function signature.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

// new type based on function type
type HelloFn func(string) error

// parameter with function type
func hello(fn func(string) error) {
// declare a function var
var pn func(string) error
pn = fn
pn("jason")

// declare a function var
var pn1 HelloFn
pn1 = fn
pn1("jason")
}

func greet(name string) error {
fmt.Println("hello", name)
return nil
}

func main() {
hello(greet)
}

main()
hello jason
hello jason

function parameter

Parameter passing is same like C except for array, for array it’s copy of the whole array, not array pointer is passed!!!

For slice even pointer is passed in function, if you append new element in that slice, the caller does not know either, see below explanations

  • the underlying array reached its capacity, a new slice created to replace the origin one, obviously the origin slice will not be modified.
  • the underlying array has not reached its capacity, and was modified. BUT the field len of the slice was not overwritten because the slice was passed by value. As a result, the origin slice will not aware its len was modified, which result in the slice not modified.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import "fmt"

func test_slice(s []string) {
s[1] = "b"
}

func test_slice2(s []string) {
// append means add an element at last, same like s[len(s)] = "c"
// if cap is smaller, a new underlay memory is created.
s = append(s, "c")
fmt.Println(s, len(s))
}

func main() {
s := make([]string, 2)
s[0] = "a"
test_slice(s)
fmt.Println(s, len(s))

// as now len(s) = 2
test_slice2(s)

// s does not see the 'c' as it's added to a new larger slice!!!
// new memory is not returned
fmt.Println(s, len(s))

//=============================================================
s1 := make([]string, 0, 2)
s1 = append(s1, "x")
// no new memory is created, but you still not see 'c'
// because the len is not update, as it's passed by value!!!

/* imagin slice as
Slice {
int len;
char *s; // underlay memory pointer
}
*/
test_slice2(s1)
fmt.Println(s1, len(s1))
}

main()
[a b] 2
[a b c] 3
[a b] 2
[x c] 2
[x] 1

flow control

for

Go has only one looping construct, the for loop, NO while, until etc.

The basic for loop has three components separated by semicolons:

  • the init statement: executed before the first iteration
  • the condition expression: evaluated before every iteration
  • the post statement: executed at the end of every iteration

*Note

  • Unlike other languages like C, Java, or JavaScript. For Go there are no parentheses surrounding the three components of the for statement but the braces { } are always required.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import "fmt"

for i := 0; i < 10; i++ { // is is only visible in this loop
if i == 5 {
continue
}
if i == 8 {
break
}
fmt.Println(i)
}

for i := 0; i < 10; { // is is only visible in this loop
if i == 5 {
continue
}
if i == 8 {
break
}
fmt.Println(i)
i++;
}

var i int
for ; i < 10; i++{
if i == 5 {
continue
}
if i == 8 {
break
}
fmt.Println(i)
}

use for as while as init and post statements are optional

1
2
3
4
5
6
7
8
9
10
sum := 0

for ; sum < 100; {
sum += 10;
}

// short way, same like while in C
for sum < 100 {
sum += 10;
}

infinite loop

1
2
for {
}

multiple assignments

1
2
3
4
a := []int{1, 2, 3, 4, 5, 6}
for i, j := 0, len(a) - 1; i < j; i, j = i + 1, j - 1 {
a[i], a[j] = a[j], a[i]
}

range with for

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import "fmt"

type Person struct {
name string
}

func main() {
ps := []Person{
{name: "a"},
}

for _, p := range ps {
p.name = "b" // p is copy of each element
}

// nothing changed
fmt.Println(ps)

for i := 0; i < len(ps); i++ {
ps[i].name = "b"
}
fmt.Println(ps)



}

# go run test.go
[{a}]
[{b}]

NOTE: when reach the loop end, the index is different!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func main() {
a := []int{1, 2, 3}
i := 0
for ; i < len(a); i++ {
// i == 3 when out of range
}
fmt.Printf("out loop index: %d\n", i)

i = 0
for i, _ = range a {
// i == 2 when out of range
}

fmt.Printf("out loop index: %d\n", i)
}


# go run test.go
out loop index: 3
out loop index: 2

if

Go’s if statements is like its for loops; the expression may not be surrounded by parentheses ( ) but the braces { } are required.

The if statement can start with a short statement to execute before the condition.

Variables declared by the statement are only in scope until the end of the if.
it’s also available inside any of the else blocks.


1
2
3
4
5
6
7
8
9
10
11
import "fmt"

if i := 0; i == 0 { // scope of i is block, after if, it's out of scope
fmt.Println("equal")
} else {
fmt.Println("i:", i)
}

if 1 { // compile error!!! as 1 is not bool type
fmt.Println("1")
}

NOTE

  • non-boolean type can NOT be used as if condition!!!

switch

The expressions need not to be constants or even integers, the cases are evaluated top to bottom until a match is found, and if the switch has no expression it switches on true.

Go’s switch is like the one in C, C++, Java, JavaScript, and PHP, except that Go only runs the selected case (implicit break at the end), not all the cases that follow. In effect, the break statement that is needed at the end of each case in those languages is provided automatically in Go but if you want to break in the middle of this case, break is required. Another important difference is that Go’s switch cases need not be constants, and the values involved need not be integers.


Switch cases evaluate cases from top to bottom, stopping when a case succeeds, auto break if matched

1
2
3
4
5
6
7
8
9
10
11
12
import "fmt"

switch i {
case 0: // auto break
case f(): //does not call f if i==0
break // break at end no effect as if there is no break here!!!
case 1:
if 2 >1 {
break // break here below does not run
}
fmt.Println("reach end of case")
}

Switch without condition
Switch without a condition is the same as switch true.

This construct can be a clean way to write long if-then-else chains.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
t := 15
switch {
case t > 10:
fmt.Println(10)
case t > 5:
fmt.Println(5)
case t > 3:
// with fallthrough, the next case condtion is not checked.
fallthrough // if t==4, fallthrough next one, no matter next condition matches or not, print 18 !!!
case t > 18:
fmt.Println(18) // break here if no other fallthrough
case t >= 4:
fmt.Println(4)
default:
}

// different cases has the same action
n := 10
switch n {
case 2,3:
// do something directly here
case 5,6:
// call do_something() to share the same action
}

goto

1
2
3
4
5
6
7
8
9
func myfunc() {
i := 0
HERE:
fmt.Println(i)
i++
if i < 10 {
goto HERE
}
}

break/continue

By default, break, continue work for inner loop, but if you want to take effect of outer loop, use label for break, continue.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import "fmt"

func greet() string {
return "hello"
}
func flowDemo() {

// for loop
for i := 0; i < 10; i++ {
if i == 5 {
continue
}

if i == 8 {
break
}
fmt.Print(i)
}

fmt.Println("\n-------------------------")
// switch, from top to bottom
switch i := "hello"; i {
case "boy":
fmt.Println("boy")
case greet():
fmt.Println("hello")
default: // always put default at last one!!!
fmt.Println("default")
}

fmt.Println("\n-------------------------")

// use break/continue with label on outer loop
here:
for i := 0; i < 2; i++ {
fmt.Println("i=", i)
for j := i + 1; j < 3; j++ {
fmt.Println("j=",j)
if i == 0 {
continue here // continue the out loop, even here is out, i is initialized for only once!!!!
}
if j == 2 {
break
}
}
}


fmt.Println("\n-------------------------")
there:
for i := 0; i < 2; i++ {
for j := i + 1; j < 3; j++ {
if j == 1 {
continue
}
fmt.Println(j)
if j == 2 {
break there // break out, no outer next loop
}
}
}
}

flowDemo()
0123467
-------------------------
hello

-------------------------
i= 0
j= 1
i= 1
j= 2

-------------------------
2

system env

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import (
"fmt"
"os"
)

func envDemo() {
// os.Environ() reutrn a map
for _, v := range os.Environ() {
fmt.Println(v)
}

os.Setenv("GO", "/tmp/go")
fmt.Println(os.Getenv("GO"))
}

envDemo()
PATH=/home/data/Anaconda3/envs/py3.9/bin:/opt/llvm/bin:/home/data/Anaconda3/envs/py3.9/bin:/home/data/Anaconda3/condabin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/home/go:/home/go/bin:/root/.yarn_pkg/bin:/usr/lib64:/usr/local/go/bin:/home/data/Anaconda3/envs/py3.9/libexec/git-core:/root/bin:/root/.yarn_pkg/bin:/home/go/bin:/home/go:/usr/local/go/bin
PWD=/
LANG=en_US.UTF-8
SHLVL=1
_=/usr/bin/env
GO111MODULE=on
GOMODCACHE=/home/go/pkg/mod
GOCACHE=/root/.cache/go-build
GOPATH=/home/go
PYDEVD_USE_FRAME_EVAL=NO
JPY_PARENT_PID=1797
GO=/tmp/go
/tmp/go

small tips

return local var from stack is safe in GO

Returning an address of a local variable is also safe.

1
2
3
4
5
6
7
8
9
import "fmt"

func test() *int {
var s int = 12
return &s
}

var p *int = test()
fmt.Println(*p)

string vs [] byte

string is the set of byte, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil.

  • Values of string type are immutable
  • Values of []byte are mutable

conversion

1
2
3
4
5
s1 := "hello"
b := []byte(s1) // new memory allocated!!!

// []byte to string
s2 := string(b) // new memory allocated!!!
1
2
3
4
5
6
7
8
9
10
11
import "fmt"

func test() {
s := "你好" //string
fmt.Println(len(s))

var s = [] rune("你好") // as rune = int32, henc two elements for 你好
fmt.Println(s, len(s), string(s))
}

test()
6
[20320 22909] 2 你好

what does empty mean for each type

let’s focus on these types, string, integer(int, uint etc), pointer, array, slice, map)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import "fmt"

func emptyDemo() {
/* default value for each type is empty
* "" --->string
* 0 --->number
* nil --->pointer
* nil --->slice
* nil --->map
* nil --->function type like function type: 'type CallBack func()'
* NO empty for array as it's has fixed size, must be set at delcaration
*/

var str string // str := ""
var n int // n := 0
var p *int // var p *int = nil
var sc []int // sc := []int{}
var m map[string]int // here m is nil can NOT add new element in it
// m := map[string]ing{} m NOT nill, can add new element, like m["a"] = 2

if str == "" {
fmt.Printf("string default value: %q \n", "")
}
if n == 0 {
fmt.Println("int default value: 0")
}
if p == nil {
fmt.Println("pointer default value: nil")
}
if sc == nil {
fmt.Printf("slice defautl value: nil ([], cap=%d, len=%d)\n", cap(sc), len(sc))
}
if m == nil {
fmt.Printf("map defautl value: nil ({} len=%d)\n", len(m))
}
}

emptyDemo()
string default value: "" 
int default value: 0
pointer default value: nil
slice defautl value: nil  ([], cap=0, len=0)
map defautl value: nil  ({} len=0)

get the size of memory for each type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"unsafe"
)

func main() {
a := int16(32)
b := 12 //int 8 bytes on 64-bit machine
c := "h" //not like C strlen.
d := 'h' //int32
var e byte = 'h' //uint8
f := &b
// f := b + 1 // f is 'i'
fmt.Printf("sizeof(%T)=%v\n", a, unsafe.Sizeof(a))
fmt.Printf("sizeof(%T)=%v\n", b, unsafe.Sizeof(b))
fmt.Printf("sizeof(%T)=%v\n", c, unsafe.Sizeof(c))
fmt.Printf("sizeof(%T)=%v\n", d, unsafe.Sizeof(d))
fmt.Printf("sizeof(%T)=%v\n", e, unsafe.Sizeof(e))
fmt.Printf("sizeof(%T)=%v\n", f, unsafe.Sizeof(f))
}

sizeof(int16)=2
sizeof(int)=8
// it's always 16 bytes, no matter how long it's, as for string, sizeof(s)==sizeof(s.len)+sizeof(s.pointer)
sizeof(string)=16
sizeof(int32)=4
sizeof(uint8)=1
sizeof(*int)=8

when should I use new()

new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T.

Suggestion, use it as less as possible, as new(T) and &T{} can do the same thing. Both allocate a zero T and return a pointer to this allocated memory. The only difference is, that &T{} doesn’t work for builtin types like int; you can only do new(int).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// without new only form is different!!
i: = 10
p := &i

//with new, it's one statement
p := new(int)

type Person struct {
name string
}

// without new
p := &Person {name: "jason"}

// with new
p := new(Person)
Person.name = "jason"

when should I use make()

It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T). The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use. A slice, for example, is a three-item descriptor containing a pointer to the data (inside an array), the length, and the capacity, and until those items are initialized, the slice is nil. For slices, maps, and channels, make initializes the internal data structure and prepares the value for use


The make built-in function allocates and initializes an object of type slice, map, or chan (only), can be used only for Slice, Map, Channel

  • Slice: The size specifies the length. The capacity of the slice is equal to its length. A second integer argument may be provided to specify a different capacity; it must be no smaller than the
    length. For example, make([]int, 0, 10) allocates an underlying array of size 10 and returns a slice of length 0 and capacity 10 that is backed by this underlying array.

  • Map: An empty map(not equal nil) is allocated with enough space to hold the specified number of elements. The size may be omitted, in which case a small starting size is allocated.

  • Channel: The channel’s buffer is initialized with the specified buffer capacity. If zero, or the size is omitted, the channel is unbuffered.


Suggestion

  • If you know the estimated size of slice or map, use make() to preallocate enough memory
  • Always use make() for channel
1
2
3
4
5
6
7
8
s := []int{}

s = append(s, 1)
s = append(s, 2) // new underlaying array is allocated.

s := make([]int, 0, 2) // cap = 2, len = 0
s = append(s, 1)
s = append(s, 2) // no new array is allocated.

pointer to array and array of pointers

1
2
3
4
5
6
7
8
9
10
11
12
13
// bad way never use this, use slice instead
func updatearray(funarr *[5]int) {
}

// good way
func updateslice(funarr []int) {
}

// bad way, use slice of pointers instead
var ptr [MAX]*int;

// good way
var ptr []*int;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

type Person struct {
Name string
}

func main() {
var pc []*Person

// initialization
pc = []*Person{
&Person{Name: "tom"},
{Name: "jack"}, //shortway
}

// after initialization
pc = append(pc, &Person{Name: "hak"})
fmt.Println(pc[0].Name, pc[1].Name, pc[2].Name)
}

main()
tom jack hak

check type of variable

1
2
3
4
5
var1 := 12
// only print it type
fmt.Printf("var1 = %T\n", var1)

fmt.Println("var1 = ", reflect.TypeOf(var1))

variable has same name with package

In such case, error happens.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"io/ioutil"
"coding.xx.com/agent/src/server/conf/xml" // package name is xml
)

func main() {
// variable name is xml
xml, err := ioutil.ReadFile("./vm.xml")
if err != nil {
fmt.Println(err)
return
}

// error!!!!
df := &xml.Domain{}

if err = df.Unmarshal(xml); err != nil {
fmt.Println(err)
return
}
}

ref