The Go Programming Language

Notes

Section 2, Program Structure

nested block in if-else if-else block

    if x, y := 100, 200; x > 1000 {
    } else if x := "hello"; y > 0 { //this x shadow the 'x' in if
        fmt.Println(x, y)
    }

scope shaw issue

    var cwd string
     func init() {
         cwd, err := os.Getwd() //compile error: the 'cwd' declared but not used
         if err != nil {
             log.Fatalf("os.Getwd failed: %v", err)
         }
    }

'cwd', 'err' are not declared in their block, so the compiler will declare them and will shadow the global 'cwd' variable.

Section 3, Basic Data Type

Go types

  • basic types: numbers, strings, booleans
  • aggregate types: arrays and structs
  • reference types: slices, maps, functions, channels. (Each includes pointers that point to internal data)
  • interface types

Types synonym

  • int/uint types' size are platform and compiler dependent. Never make assumption for the size!
  • 'rune' is synonym to 'int32', while 'byte' to 'uint8'
  • 'uintptr' is for the low level programming, like Go with C

Operators

  • the '%' for negative:
    fmt.Println(-5 % -2) //-1
    fmt.Println(5 % -2) //1
    fmt.Println(-5 % 2) //-1
  • the result is the same type as the operators, so overflow may happen
    var u uint8 = 255
    fmt.Println(u, u+1, u*u) // "255 0 1"
    var i int8 = 127
    fmt.Println(i, i+1, i*i) // "127 -128 1"
  • 'unary operators': '+', '-', '^'

    ** For integers, '+x' is short for '0 + x' while '-x' is short for '0 - x'
    ** For floating and complex numbers, '+x' is just for 'x' while '-x' is the negative of 'x'
    ** '^x' return the value with each bit inverted

  • 'NaN' is from like '0/0' or 'sqrt(-1)'. Any comparation of 'NaN' always yield false

    nan := math.NaN()
    fmt.Println(nan == nan, nan < nan, nan > nan) // "false false false"
  • Row string literal: no escape happens and can cross multiple lines

Conversion and format

Printf

    o := 0666
    fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
    x := int64(0xdeadbeef)
    fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
    // Output:
    // 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
  • The '[1]' means use the first argument, so no need to provide the same argument again and again

  • The '#' is used to add the '0', '0x', '0X'

  • If space is after the '%', like '% x', then it will insert the space for each hex digits, like 'e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0'

  • The 'strconv' package includes many format functions

  • The '%t' show true or false, '%T' show the type

Unicode

  • Unicode version 8 use 4 bytes for each charactor, also knows as UTF-32/UCS-4. In Go, the 'rune' is used for this.

  • Unicode wasts lots of space, so the 'UTF-8' is invented.

    00xxxxxx runes0−127 (ASCII)
    111xxxxx 10xxxxxx 128−2047 (values <128 unused)
    1110xxxx 10xxxxxx 10xxxxxx 2048−65535 (values <2048 unused)
    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536−0x10ffff (other values unused)
    
  • Code point

    Same values:

    "世界"
    "\xe4\xb8\x96\xe7\x95\x8c" //the binary using the UTF-8 coding
    "\u4e16\u754c" //the binary using the Unicode, will be encoded as UTF-8 by the compiler
    "\U00004e16\U0000754c" //the binary using the Unicode, will be encoded as UTF-8 by the compiler
    

    They are all valid UTF-8 encoding of code point, but in Go, when for 'rune', value below 256 can used as '\x', while above 256, must use '\u' or '\U'.

enumeratio

const (
    _ = 1 << (10 * iota)
    KiB // 1024
    MiB // 1048576
    GiB // 1073741824
    TiB // 1099511627776 (exceeds 1 << 32)
    PiB // 1125899906842624
    EiB // 1152921504606846976
    ZiB // 1180591620717411303424
    YiB // 1208925819614629174706176
)

The 'iota' is 'int' type, so it will overflow.

Section 4, Composite Types

About array type

  • The size is part of the type, so '[3]int' is different type from '[5]int'

  • Can init with specific indices

    symbol := [...]string{0: "$", 2: "9", 5: "!", 9: """}
    r := [...]int{99: -1}
    

    All the ones not in the indices will be init as the zero value.

  • Arrays are comparable, 'equal' when the two arrays have the same type and all the elements are the same

About the slice type

  • Index beyonds the capcity will cause panic, while beyonds length will expend the slice

  • 's[i:j]' will share the same underlying array with slice 's'

  • Slices are not comparable. For '[]byte], using the 'bytes.Equal' to compare

  • Nil slice has zero length and zero capcity, while the reverse is not right.

    var s []int    // len(s) == 0, s == nil
    s = nil        // len(s) == 0, s == nil
    s = []int(nil) // len(s) == 0, s == nil
    s = []int{}    // len(s) == 0, s != nil
    
  • Nil slice and non-nil slice with zero length should be treated in the same way, so using 'len(s) == 0' to check if a slice is empty

  • Under the hood, 'make' creates an unnamed array variable and returns a slice of it

  • With append, slice may enlarge its space to hold new elements. The copy will handle the overlap of the underlying array.

About the map type

  • Lookup using a key not existing will return the zero value of the value type.

    a := make(map[string]int)
    a["bob"] = a["bob"] + 1
    fmt.Println(a["bob"]) //will output 1
    
  • You can use the '++' to increase the value

    a := make(map[string]int)
    a["bob"]++
    fmt.Println(a["bob"]) //will output 1
    
  • A map element is not variable, you can never try to get its address. But on the different, you can get the address of a slice element

    _ = &a["bob"] //compile error
    
  • Zero value of the map is nil.

    var ages map[string]int
    fmt.Println(ages == nil)    // "true"
    fmt.Println(len(ages) == 0) // "true"
    
  • A nil map supprts operations: lookup, delete, len, range, but not store value.

    var aa map[string]int
    aa["bob"] = 100 //panic, the map must be created before storing any value
    
  • The maps are not comparable except for comparision with nil.

About the struct type

  • Will compile error

    func create(id int) Employee {
    //...
    }
    create(100).Name = "bob" //compile error, change the return type to *Employee will be ok
    
  • Fields not exported in a struct cannot be inited in another package

    package p
    type T struct{ a, b int } // a and b are not exported
    
    
    package q
    import "p"
    var _ = p.T{a: 1, b: 2} // compile error: can't reference a, b
    var _ = p.T{1, 2}       // compile error: can't reference a, b
    
    

    And this is also wrong:

    package p
    type T struct{ A, b, C int } // a and b are not exported
    
    
    package q
    import "p"
    var _ = p.T{1, 2}       // compile error: can't reference A, b
    var _ = p.T{A: 1, C: 2} // ok
    
  • Embedding will form an anoymous field of the struct with the implicit field name of the embedded type.

    type Point struct {
        X, Y int
    }
    
    type Circle struct {
        Point // form a anoymous field with implicit field name "Point"
        Radius int
    }
    
    type Wheel struct {
        Circle // form a anoymous field with implicit field name "Circle"
        Spokes int
    }
    

    So you cannot embed the same type for more than twice, as their implicit names will conflict.

  • The implicit name can be optional for dot expression

    w := new(Wheel)
    w.Circle.Radius = 100
    w.Radius = 100 // Both of the two are valid
    
  • The embedded struct cannot init by normal struct literal

    w = Wheel{8, 8, 5, 20}                       // compile error: unknown fields
    w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields
    

    You must init like this:

    w = Wheel{Circle{Point{8, 8}, 5}, 20}
    w = Wheel{
      Circle: Circle{
           Point:  Point{X: 8, Y: 8},
      Radius: 5,
      },
      Spokes: 20, // NOTE: trailing comma necessary here (and at Radius)
    }
    fmt.Printf("%#v\n", w)
    // Output:
    // Wheel{Circle:Circle{Point:Point{X:8, Y:8}, Radius:5}, Spokes:20}
    w.X = 42
    fmt.Printf("%#v\n", w)
    // Output:
    // Wheel{Circle:Circle{Point:Point{X:42, Y:8}, Radius:5}, Spokes:20}
    

About the JSON

  • field tag is a string of metadata associated at compile time with the field of a struct. It's a 'key:"value"' pair.

    Year  int  `json:"released"`
    Color bool `json:"color,omitempty"`
    
  • The JSON field tag format: json:"<json name>,[addition option]"

  • When Marshal, the not exported fields will be ignored; When Unmarshal, the fields of JSON that are not in the data struct will be ignored

  • Associating JSON names with Go struct names during Unmarshaling is case-insensitive, so no need to add the JSON field tag for simple field name. But for the Marshal, you must define the JSON field tag, or the capital name will be used.

Section 4, Functions

Function definition

  • If return only one unamed type, the '()' can be omitted in the return result definition.

  • Parameters and named results share the same level of the function outermost block.

Go function stack

  • Go has a variable size function stack, so the recursive is always safe.

About the defer

  • The 'defer' forms a stack and wil be called by stack before the function return.

  • Even panic in the function, the 'defer' will be called too.

Section 6, Methods

About the receiver

  • The compiler will perform implicit '&p' on the variable when the receiver is the pointer.

    type Point struct {
        x int
        y int
    }
    
    func (p *Point) Offset(off int) {
        p.x += off
        p.y += off
    }
    
    //...
    p := Point{}
    p.Offset(10) // This is valid, as the compiler will perform implicit '&p' on this.
    Point{1, 2}.Offset(100) // compile error, as it's not the variable and cannot be addressed.
    
  • It's true on these three scenarios:

    1. The type of the variable is the same as the receiver parameter
    2. The type of the variable is T, while the receiver parameter is *T (The compiler will implicitly perform '&p')
    3. The type of the variable is T, while the receiver parameter is T (The compiler will implicitly perform 'p')
  • Bind the method with the receiver and assign to a variable, then you can call the method without the receiver via the variable. This is called method value

    p := Point{1, 2}
    q := Point{4, 6}
    distanceFromP := p.Distance
    fmt.Println(distanceFromP(q))
    
  • Assigning the method to variable directly is called method expression, you can then call it by providing the receiver as the first parameter.

    p := Point{1, 2}
    q := Point{4, 6}
    distance := Point.Distance   // method expression
    fmt.Println(distance(p, q))  // "5"
    

About the embedding

  • Embedding a type will inherit all its methods. In terms of implementation, the compiler will generate the wrapped methods with the type.

About the encapsulation

  • All fields in the struct is visible to any function or any method in the same package

Section 7, Interfaces

About the interface satisfaction

  • Though you can use the type T to access *T method (the compiler will perform it implicitly), but you cannot use type T to satisfy the *T type interface.

interface values

  • An interface value is composed of: dynamic type and dynamic value. The type is the dynamic type of the value.

    var w io.Writer // both the type and value are nil
    if w == nil { // this is true
        fmt.Println('is nil')
    }
    
    var buf *bytes.Buffer // buf is a nil pointer
    w = nil // both the type and value are nil
    w = buf // the type is '*bytes.Buffer' (not nil), while the value is nil
    if w == nil { // this is false, as the type of w is not nil, so the w is not nil
        fmt.Println('is nil')
    }
    
    
  • interface value comparation may cause panic

    var x interface{} = []int{1, 2, 3}
    fmt.Println(x == x) // panic: comparing uncomparable type []int
    

    When comparation, both the type and value must be comparable. Similar risk eixsts when using the interfaces as the map keys.

  • To report the dynamic type of an interface value, using the '%T'

    var w io.Writer
    fmt.Printf("%T\n", w) // "<nil>"
    w = os.Stdout
    fmt.Printf("%T\n", w) // "*os.File"
    w = new(bytes.Buffer)
    fmt.Printf("%T\n", w) // "*bytes.Buffer"
    

Type assertion

  • x.(T), if T is a concrete type, it will check if x's dynamic type is identical to T, if so the results is the dynamic value, or it will panic. If the T is a interface type, it will first check if x's dynamic type satisfies T, if so, a new interface value will be returned with the new dynamic type T and the same dynamic value.

  • No matter what type was asserted, if the operand is a nil interface value, the type assertion fails

Type switch

  • take one example, you can assign the x.(type) to a new variable

    func sqlQuote(x interface{}) string {
             switch x := x.(type) {
             case nil:
                 return "NULL"
             case int, uint:
                 return fmt.Sprintf("%d", x) // x has type interface{} here.
             case bool:
                 if x {
                     return "TRUE"
                }
                 return "FALSE"
             case string:
                 return sqlQuoteString(x) // (not shown)
             default:
                panic(fmt.Sprintf("unexpected type %T: %v",wxw,wx.i)t)-ebooks.info }
    }
    

Section 8, Goroutines and Channels

Channels

  • The channel is a reference.

  • channels are comparable, it's true when both are references to the same channel data structure.

  • A channel can be closed, send to a closed channel will cause panic, while receive on a closed channel, it will yield zero value of the channel element type after the channel is empty.

  • To test on a closed channel, use like x, ok := <- c, to be comvinent, you can use the 'range' to loop the channel until it's drain and closed.

    for x := range naturals { // the loop will exit when the channel 'naturals' is drained and closed.
        squares <- x * x
    }
    
  • It's not necessary to close the channel when you finish with it. Only close it when you want to tell the receive that you will sent anymore data. It's different from the file, you must always close a file after finishing with it.

  • Unidirectional channels: the chan<- int is send-only channel, the <-chan int is the receive-only channel. And the close must not be applied on the receive-only channel. It will implicitly convert chan int to chan<- int and <-chan int.

  • To get the capcity of the channel, using the cap(c), while the len(c) returns the currently number of the buffered elements.

  • An typical example for loop paralle

    func makeThumbnails6(filenames <-chan string) int64 {
             sizes := make(chan int64)
             var wg sync.WaitGroup // number of working goroutines
             for f := range filenames {
                 wg.Add(1)
                 // worker
                 go func(f string) {
                     defer wg.Done()
                     thumb, err := thumbnail.ImageFile(f)
                     if err != nil {
                         log.Println(err)
                             return
                             }
                     info, _ := os.Stat(thumb) // OK to ignore error
                     sizes <- info.Size()
                 }(f)
             }
             // closer
             go func() {
                 wg.Wait()
                 close(sizes)
             }()
             var total int64
             for size := range sizes {
                 total += size
             }
             return total
         }
    
  • Using a buffered channel as a semaphore

    var sema = make(chan struct{}, 10)
    func dowork() {
        sema <- struct{}{} // acquire the sema
        defer func() { <-sema }() // release the sema
        // do something here
        //...
    }
    
  • Using select with a closeable channel to cancel the goroutines. Closing the channel is like broadcasting the goroutines to let them exit gracefully.

    func main() {
        done := make(chan struct{})
        jc := make(chan string)
        go func() {
            select {
            case <-done:
                return
            case job := <-jc:
                //do something with the job
            }
        }()
    
        jc<- "hello"
        close(done)
    }
    

Section 9, Concurrency with Shared Variables

Mutex

  • defer will be be executed even the func panic

  • The mutex is not re-entrant, which means that it can not be locked recursively.

  • Different goroutines may run on different CPU, different statements may be reordered by the modern compiler and CPU when they are not dependent on each other.

         var x, y int
         go func() {
             x = 1 // A1
             fmt.Print("y:", y, " ") // A2
         }()
         go func() {
             y = 1                   // B1
             fmt.Print("x:", x, " ") // B2
         }()
    

    The result may be "x = 0, y = 0", as the CPU or compiler may reorder the statements; the different goroutines may run on different CPU, and the update may happen on each CPU cache and doesn't sync with main memory on time. The mutex will make sure the right order.

  • Duplicate suppression

    type entry struct {
             res   result
             ready chan struct{} // closed when res is ready
         }
    
    func New(f Func) *Memo {
         return &Memo{f: f, cache: make(map[string]*entry)}
    }
    
    type Memo struct {
        f     Func
        mu    sync.Mutex // guards cache
        cache map[string]*entry
    }
    
    func (memo *Memo) Get(key string) (value interface{}, err error) {
        memo.mu.Lock()
        e := memo.cache[key]
        if e == nil {
            // This is the first request for this key.
            // This goroutine becomes responsible for computing
            // the value and broadcasting the ready condition.
            e = &entry{ready: make(chan struct{})}
            memo.cache[key] = e
            memo.mu.Unlock()
            e.res.value, e.res.err = memo.f(key)
            close(e.ready) // broadcast ready condition
        } else {
            // This is a repeat request for this key.
            memo.mu.Unlock()
            <-e.ready // wait for ready condition
        }
        return e.res.value, e.res.err
    }
    

Goroutines vs OS threads

  • Goroutine has dynamic size of stack, which is up to 1GB while the OS thread is typical 2MB.

  • Goroutines schedule implicitly by certain Go language constructs, like time.Sleep, channel block, mutex block, etc, no need to switch kernel context. While the OS threads schedule is invoked every few ms, need to switch kernel context.

  • GOMAXPROCS is the default the number of the CPU on the machine.

Section 10, Packages and The Go Tool

Go build

  • Using flag '-u' in 'go get' will update the current repo to the latest version.

  • The 'go build -i' will install the dependent packages and will decrease the compile time next time.

  • Cross compile

    $ GOARCH=386 go build -i gopl.io/ch10/cross // it will install in $GOPATH/pkg/386
    
  • Files like net_linux.go or asm_amd64.s, the compiler will compile it based on the env.

  • Comment before the file package declaration can also control the compiling. // +build linux darwin means only compile it for linux and darwin, // +build ignore means never compile this file.

Go doc

  • The first sentence is usually a summary that starts with the declared name.

    // Fprintf formats according to a format specifier and writes to w.
    // It returns the number of bytes written and any write error encountered.
    func Fprintf(w io.Writer, format string, a ...interface{}) (int, error)
    
  • Check the doc

    $ go doc json.encode
    func (dec *Decoder) Decode(v interface{}) error
        Decode reads the next JSON-encoded value from its input and stores
        it in the value pointed to by v.
    
  • Export the doc to html and serve it

    $ godoc -http :8000
    

    And then, access the doc via http://localhost:8000/pkg

Internal packages

  • package under the 'internal' directory is the internal package

  • Can be seen by the packages under the parent of the 'internal' directory

    net/http
    net/http/internal/chunked
    net/http/httputil
    net/url
    

    The 'net/http/internal/chunked' can be seen by 'net/http', 'net/http/httputil', while not seen by 'net/url'

Go list

  • list the packages

  • List all the packages under the workspace.

    $ go list ...
    
  • List all under path

    $ go list gopl.io/ch3/...
    
  • List by matched pattern

    $ go list ...xml...
    encoding/xml
    gopl.io/ch7/xmlselect
    
  • go list -json to show by json format of the package detail info.

    $ go list -json hash
    {
        "Dir": "/home/gopher/go/src/hash",
            "ImportPath": "hash",
            "Name": "hash",
            "Doc": "Package hash provides interfaces for hash functions.",
            "Target": "/home/gopher/go/pkg/darwin_amd64/hash.a",
            "Goroot": true,
            "Standard": true,
            "Root": "/home/gopher/go",
            "GoFiles": [
                "hash.go" ],
                "Imports": [
                    "io"
                ],
                "Deps": [
                    "errors",
                "io",
                "runtime",
                "sync",
                "sync/atomic",
                "unsafe"
                ]
    }
    
  • '-f ' to customize the output format

    $ go list -f '{{join .Deps " "}}' strconv
     errors math runtime unicode/utf8 unsafe
    

    It lists all the dependencies. Actually, the '-f' is like using the text template to format the output.

Section 11, Testing

  • Within *_test.go files, three kinds of functions are treated specially : tests, benchmarks, and examples. Function starts with 'Test' is for tests, 'Benchmark' is for benchmarks, 'Example' is for examples.

Tests

  • '-run' to run specific cases using the regular expression

    $ go test -v -run="French|Canal"
         === RUN TestFrenchPalindrome
         --- FAIL: TestFrenchPalindrome (0.00s)
             word_test.go:28: IsPalindrome("été") = false
         === RUN TestCanalPalindrome
         --- FAIL: TestCanalPalindrome (0.00s)
             word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false
         FAIL
         exit status 1
         FAIL    gopl.io/ch11/word1  0.014s
    

External Test Package

  • Sometimes, in case of a cycle of dependencies in test files, use the _test to declare a external test package. For example, in 'net/url', the test file may declare package like package url_test, the compiler will create an external package for this implicitly.

    $ go list -f={{.GoFiles}} fmt
         [doc.go format.go print.go scan.go]
    $ go list -f={{.TestGoFiles}} fmt
         [export_test.go]
    $ go list -f={{.XTestGoFiles}} fmt
         [fmt_test.go scan_test.go stringer_test.go]
    

    The 'XTestGoFiles' is the external test package files.

  • In external package test file, you cannot access the private variable or functions in the tested package. In order to do while-box testing in external package test files, using a export file to export the private things. And this file is always called export_test.go

    For example, the export_test.go file for the 'fmt' package is:

    package fmt
    var IsSpace = isSpace
    

Benchmark Testing

  • Test and check the memory

    $ go test -bench=. -benchmem
         PASS
              BenchmarkIsPalindrome    2000000    807 ns/op  128 B/op  1 allocs/op
    

Profiling

  • Profiling when testing

    $ go test -cpuprofile=cpu.out
    $ go test -blockprofile=block.out
    $ go test -memprofile=mem.out
    

    And then run the pprof tool

        $ go test -run=NONE -bench=ClientServerParallelTLS64 \
                 -cpuprofile=cpu.log net/http
         PASS
         BenchmarkClientServerParallelTLS64-8  1000
            3141325 ns/op  143010 B/op  1747 allocs/op
         ok      net/http       3.395s
         $ go tool pprof -text -nodecount=10 ./http.test cpu.log
         2570ms of 3590ms total (71.59%)
         Dropped 129 nodes (cum <= 17.95ms)
         Showing top 10 nodes out of 166 (cum >= 60ms)
             flat  flat%   sum%     cum   cum%
           1730ms 48.19% 48.19%  1750ms 48.75%  crypto/elliptic.p256ReduceDegree
            230ms  6.41% 54.60%   250ms  6.96%  crypto/elliptic.p256Diff
            120ms  3.34% 57.94%   120ms  3.34%  math/big.addMulVVW
            110ms  3.06% 61.00%   110ms  3.06%  syscall.Syscall
             90ms  2.51% 63.51%  1130ms 31.48%  crypto/elliptic.p256Square
             70ms  1.95% 65.46%   120ms  3.34%  runtime.scanobject
             60ms  1.67% 67.13%   830ms 23.12%  crypto/elliptic.p256Mul
             60ms  1.67% 68.80%   190ms  5.29%  math/big.nat.montgomery
             50ms  1.39% 70.19%    50ms  1.39%  crypto/elliptic.p256ReduceCarry
             50ms  1.39% 71.59%    60ms  1.67%  crypto/elliptic.p256Sum
    

    The -nodecount=10 means showing only 10 rows

Example Testing

  • For example test ExampleFuncName, the go doc will add this example to the function FuncName automatically.

Section 12, Reflection

reflect, type and value

  • reflect.TypeOf return the dynamic type of the interface value. The '%T' in fmt is using this.

    var w io.Writer = os.Stdout
    fmt.Println(reflect.TypeOf(w)) // "*os.File"
    
  • reflect.ValueOf to get the value

    v := reflect.ValueOf(3) // a reflect.Value
    fmt.Println(v)          // "3"
    fmt.Printf("%v\n", v)   // "3"
    fmt.Println(v.String()) // NOTE: "<int Value>"
    t := v.Type() // a reflect.Type
    fmt.Println(t.String()) // "int"
    v := reflect.ValueOf(3) // a reflect.Value
    x := v.Interface() // an interface{}
    i := x.(int) // an int
    fmt.Printf("%d\n", i) // "3"
    
  • reflect.Value.Kind usage. Kind of zero value is reflect.Invalid

    func formatAtom(v reflect.Value) string {
             switch v.Kind() {
             case reflect.Invalid:
                 return "invalid"
             case reflect.Int, reflect.Int8, reflect.Int16,
                 reflect.Int32, reflect.Int64:
                 return strconv.FormatInt(v.Int(), 10)
             case reflect.Uint, reflect.Uint8, reflect.Uint16,
                 reflect.Uint32, reflect.Uint64, reflect.Uintptr:
                 return strconv.FormatUint(v.Uint(), 10)
             // ...floating-point and complex cases omitted for brevity...
             case reflect.Bool:
                 return strconv.FormatBool(v.Bool())
             case reflect.String:
                 return strconv.Quote(v.String())
             case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
                 return v.Type().String() + " 0x" +
                     strconv.FormatUint(uint64(v.Pointer()), 16)
             default: // reflect.Array, reflect.Struct, reflect.Interface
                 return v.Type().String() + " value"
    } }
    
  • reflect for the Composite Types

    func display(path string, v reflect.Value) {
             switch v.Kind() {
             case reflect.Invalid:
                 fmt.Printf("%s = invalid\n", path)
             case reflect.Slice, reflect.Array:
                 for i := 0; i < v.Len(); i++ {
                     display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
                 }
             case reflect.Struct:
                 for i := 0; i < v.NumField(); i++ {
                     fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
                     display(fieldPath, v.Field(i))
                 }
             case reflect.Map:
                 for _, key := range v.MapKeys() {
                     display(fmt.Sprintf("%s[%s]", path,
                         formatAtom(key)), v.MapIndex(key))
                 }
             case reflect.Ptr:
                 if v.IsNil() {
                     fmt.Printf("%s = nil\n", path)
                 } else {
                     display(fmt.Sprintf("(*%s)", path), v.Elem())
                 }
             case reflect.Interface:
                 if v.IsNil() {
                     fmt.Printf("%s = nil\n", path)
                 } else {
                     fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
                     display(path+".value", v.Elem())
                 }
             default: // basic types, channels, funcs
                 fmt.Printf("%s = %s\n", path, formatAtom(v))
             }
    }
    
  • Set value

    x := 1
    rx := reflect.ValueOf(&x).Elem()
    rx.SetInt(2) // OK, x = 2
    rx.Set(reflect.ValueOf(3)) // OK, x = 3
    rx.SetString("hello") // panic: string is not assignable to int
    rx.Set(reflect.ValueOf("hello")) // panic: string is not assignable to int
    var y interface{}
    ry := reflect.ValueOf(&y).Elem()
    ry.SetInt(2) // panic: SetInt called on interface Value
    ry.Set(reflect.ValueOf(3)) // OK, y = int(3)
    ry.SetString("hello") // panic: SetString called on interface Value
    ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"
    
  • We can use reflect to access the unexport fields of a struct, but you cannot update it, because the reflect will records whether it's exported or not.

    stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var
    fmt.Println(stdout.Type())                  // "os.File"
    fd := stdout.FieldByName("fd")
    fmt.Println(fd.Int()) // "1"
    fd.SetInt(2)          // panic: unexported field
    fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"
    
  • access the struct tags

    // Build map of fields keyed by effective name.
        fields := make(map[string]reflect.Value)
        v := reflect.ValueOf(ptr).Elem() // the struct variable
        for i := 0; i < v.NumField(); i++ {
            fieldInfo := v.Type().Field(i) // a reflect.StructField
            tag := fieldInfo.Tag           // a reflect.StructTag
            name := tag.Get("http")
            if name == "" {
                name = strings.ToLower(fieldInfo.Name)
            }
            fields[name] = v.Field(i)
        }
    
  • access the methods. Using reflect.Value.Call is possible to call the method

    // Print prints the method set of the value x.
     func Print(x interface{}) {
         v := reflect.ValueOf(x)
         t := v.Type()
         fmt.Printf("type %s\n", t)
         for i := 0; i < v.NumMethod(); i++ {
             methType := v.Method(i).Type()
             fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
                 strings.TrimPrefix(methType.String(), "func"))
        }
    }
    
  • Reflect is fragile and will cause terrible performance issue is it's in the critical code path.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容