Golang Tripping Hazards


Every time I learn something new, I always end up finding things that I really wish someone had told me at the beginning. If you're starting to learn Go, perhaps you'll find these tidbits useful.

We hope that by sharing this list we can save new Go developers some time hitting their head against the keyboard. If you'd like to contribute something to the list, please send a Pull request.

Compiled by Dan Miles: Twitter, GitHub

If you'd like to see some of Monsoon Commerce's Open Source projects we've released in Go, check out:

  1. https://github.com/monsooncommerce/mockstomp We use it to mock a connection to a STOMP-compatable message broker for unit testing purposes

  2. https://github.com/monsooncommerce/log A logging library (yes, another one) that allows for networked-logging to a STOMP-compatable message broker

  3. https://github.com/monsooncommerce/stompWriter A short and simple Writer implementation we use with the logging library to write to STOMP-compatable message brokers

  4. https://github.com/monsooncommerce/mockwriter A way to mock-up a writer for the logger when unit testing.

  5. https://github.com/monsooncommerce/cryptoutil A simple wrapper to simplify some of Go's built-in cryptographic features (right now it only supports AES encrypt/decrypt). It has been pointed out to me that https://godoc.org/golang.org/x/crypto/nacl/secretbox is the more canonical solution, but feel free to use this, too.


The defer statement: A reserved word in Go that causes a function to execute as the last thing that happens when a function returns. You can stack as many as you want and they execute in the reverse order that they were declared. It looks something like this:

func myFunction(intArg int, stringArg string) (int, error) {
    networkConn := openNetworkConnection()
    defer networkConn.Close()
    if intArg < 0 {
        errMsg := fmt.Sprintf("myFunction can only deal with positive integers, invalid argument: %v", intArg)
        return 0, errors.New(errMsg) // right after this, your deferred networkConn.Close() will execute
    }
    return intArg, nil // right after this, your deferred networkConn.Close() will execute
}

However, there's a complication. If the function you name in your defer statement takes arguments, and if those arguments are the return values of other functions, the argument-functions execute at the time you declare the defer, and the deferred function itself executes at function exit:

func myFunction(intArg int, stringArg string) (int, error) {
    networkConn := openNetworkConnection()
    //determineHowConnShouldBeClosed() executes NOW, its return-value is stored as an
    // argument to closeMyNetworkConnection when it finally gets run.
    defer closeMyNetworkConnection(networkConn, determineHowConnShouldBeClosed())
    if intArg < 0 {
        errMsg := fmt.Sprintf("myFunction can only deal with positive integers, invalid argument: %v", intArg)
        return 0, errors.New(errMsg) // right after this, your deferred networkConn.Close() will execute
    }
    return intArg, nil // right after this, your deferred networkConn.Close() will execute
}

A deferred function will never be executed if you os.Exit() or log.Fatal() explicitly:

func main() {
  defer func() {
    fmt.Println("Hello World!") // The anonymous func will not be executed
  }()
  os.Exit(1)
}

Interfaces are always pointers: Consider the following example:

package main

import (
    "fmt"
)

type Fooer interface {
    Foo() int
}

type ImplementsFooer struct{
    DataMember int
}

// provide the function foo to make your ImplementsFooer struct
// implement Fooer. Note that this function has a non-pointer
// receiver to an ImplementsFooer instance, we'll get to that
// later
func (f ImplementsFooer) Foo() int {
    return f.DataMember
}

func WantsPointerToFooer(f *Fooer) {
    fmt.Printf("I got a fooer, result of Foo() is %v\n", f.Foo())
}

func main(){
    var myInstance Fooer // declare as type Fooer
    myInstance = ImplementsFooer{DataMember: 5}
    WantsPointerToFooer(&myInstance)
}

It doesn't compile: *prog.go:24: f.Foo undefined (type Fooer is pointer to interface, not interface). This is because myInstance is of type interface, which is already a pointer. WantsPointerToFooer should actually accept (f Fooer) and you'll still get the pass-by-reference performance you're looking for.


Pointer recievers, non-pointer receivers and the implementation of interfaces

As you program your way through some of your first go projects, you'll likely discover, as I did, that you can add instance-methods to structs. In this way, you can get some object-like stuff going on. You might do it like this in python:

class Foo:
    data_member = 1
    def increment_data_member(self):
        self.data_member += 1

and in Go, it looks like this:

type Foo struct {
    DataMember int
}
func (f *Foo) IncrementDataMember() {
    f.DataMember++
}

Notice that the method, IncrementDataMember is made a "part" of the Foo struct with the "reciever" between the func and the IncremenentDataMember? That receiver is a pointer-receiver and if you're like me, you just internalized that as the way things are, but it goes a little deeper.

Pointers and the base type they point to have distinct method sets in Go, and this affects how you implement an interface. Let's use our implementation of the Fooer interface in the previous example:

type Fooer interface {
  Foo() int
}

type ImplementsFooer struct{
  DataMember int
}

// this receiver is NOT a pointer
func (f ImplementsFooer) Foo() int {
  return f.DataMember
}

ImplementsFooer and the pointer to that type, *ImplementsFooer are distinct types in Go, and in this example only one of them implements the Fooer interface.

Given that information, please look at this sample program:

package main
import "fmt"

type Fooer interface {
  Foo() int
}

type ImplementsFooer struct{
  DataMember int
}

// implement the Fooer interface
func (f *ImplementsFooer) Foo() int {
  return f.DataMember
}

func main() {
  implFoo := ImplementsFooer{35}
  printFoo(implFoo)
}

// this function requires a
// type that implements Fooer
func printFoo(f Fooer) {
  fmt.Println(f.Foo())
}

This program will not compile. The compiler will give you an error when you try to compile it:

cannot use implFoo (type ImplementsFooer) as type Fooer in argument to printFoo:
  ImplementsFooer does not implement Fooer (Foo method has pointer receiver)

In this case, the *ImplementsFooer type implements the interface, but the ImplementsFooer type does not – structs and a pointer to that struct are have two distinct method sets in Go.

The simple fix for this example program is to pass a pointer into the printFoo function, like so:

func main() {
  implFoo := ImplementsFooer{35}
  printFoo(&implFoo)
}

The := symbol: It's not an emoticon it's an assignment operator that automatically infers the type. In this way:

var foo int
foo = 5

is equivalent to:

foo := 5

but if you're not careful, it can mess with your scoping:

foo, err := someFunction() // in this case, err is nil
if true {
    bar, err := someOtherFunction() // in this case, err is not nil, but it will
                                        // go out of scope at the end of the block
}
// out here, we're dealing with the original (nil) declaration of err
if err != nil {
    fmt.Printf("we had an error")
}

(this prints nothing)

if you find yourself writing something like that, you probably meant this instead:

foo, err := someFunction() // err is nil
if true {
    var bar int
    bar, err = someOtherFunction() //err is not nill
}
if err != nil {
    fmt.Printf("we had an error")
}

(this prints we had an error)


Pointers Pointers are always a tripping hazard for new developers, and Go is no exception. Instead of re-inventing that blog, I recommend you just go read this: https://www.golang-book.com/books/intro/8


The Go compiler is more picky about style than other languages you may have worked with: I don't have much concrete advice here, but it's something that bugged me when I started. If you're someone who likes your curly brackets ({) on the next line, that's a compile error, and you just have to learn to live with it. Fortunately, there's an automatic formatter called "fmt" that fixes everything for you. On the commandline, run: go fmt and it will fix the formatting in all of your go files.


Same-line statements are possible but they can get confusing for scope reasons: Go allows you to put multiple statements on the same line separated by semi-colons. I haven't seen this often, but there are a couple of cases where I do. You can use it to check to see if something is in a map:

myMap := make(map[string]string)
myMap["foo"] = "bar"
if value, ok := myMap["baz"]; !ok {
    // the "truthiness" of this if statement is in the last (right-most) statement
    fmt.Printf("baz does not seem to be in myMap\n")
} else {
    fmt.Printf("we found baz, its value is %v\n", value)
}
// be aware that value is not defined in this scope, outside the if/else block

And this will check to see if an operation returned an error:

if result, err := someFunction(); err != nil {
    fmt.Printf("had a problem running someFunction. It returned a value of %v and an error, %v\n", result, err.Error())
}
// be aware that because result is declared inside an if-statement, it is not in scope outside the if-block.

Postfix Increments: They're statements, not operators as they are in other languages. fmt.Printf("%v\n", foo++) doesn't work.


Range operator doesn't do exactly what I expected: It seems to give you a strange, quasi-pointer, not a real reference. Take this example:

package main

import (
    "fmt"
)

func main() {
    mySlice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    myChan := make(chan *int, 10)

    for _, theInt := range mySlice {
        fmt.Printf("putting a pointer to %v on the chan\n", theInt)
        myChan <- &theInt
    }

    for i := 1; i <= 10; i++ {
        thePtr := <-myChan
        if *thePtr != i {
            fmt.Printf("pulled %v off the chan, expected %v\n", *thePtr, i)
        }
    }
}

You might expect it to put each of the members of the mySlice slice on the channel, but it doesn't. The output is:

putting a pointer to 1 on the chan
putting a pointer to 2 on the chan
putting a pointer to 3 on the chan
putting a pointer to 4 on the chan
putting a pointer to 5 on the chan
putting a pointer to 6 on the chan
putting a pointer to 7 on the chan
putting a pointer to 8 on the chan
putting a pointer to 9 on the chan
putting a pointer to 10 on the chan
pulled 10 off the chan, expected 1
pulled 10 off the chan, expected 2
pulled 10 off the chan, expected 3
pulled 10 off the chan, expected 4
pulled 10 off the chan, expected 5
pulled 10 off the chan, expected 6
pulled 10 off the chan, expected 7
pulled 10 off the chan, expected 8
pulled 10 off the chan, expected 9

So theInt, as defined in that range object, is safe to work with inside the scope of the for statement, but pointers to it seem to change later. So for a use case like this, it's safer simply to iterate:

for i := 0; i < len(mySlice); i++ {
    myChan <- &mySlice[i]
}


高隆跳闸危害


Every time I learn something new, I always end up finding things that I really wish someone had told me at the beginning. If you're starting to learn Go, perhaps you'll find these tidbits useful.
我们希望通过共享这个列表,我们可以节省新的Go开发人员一些时间击中键盘。如果您想向列表中提供任何内容,请发送拉链请求。

由Dan Miles编译: Twitter GitHub

如果您想查看我们在Go发布的一些季风商务开源项目,请查看:

  1. https://github.com/monsooncommerce/mockstomp We use it to mock a connection to a STOMP-compatable message broker for unit testing purposes

  2. https://github.com/monsooncommerce/log A logging library (yes, another one) that allows for networked-logging to a STOMP-compatable message broker

  3. https://github.com/monsooncommerce/stompWriter A short and simple Writer implementation we use with the logging library to write to STOMP-compatable message brokers

  4. https://github.com/monsooncommerce/mockwriter A way to mock-up a writer for the logger when unit testing.

  5. https://github.com/monsooncommerce/cryptoutil A simple wrapper to simplify some of Go's built-in cryptographic features (right now it only supports AES encrypt/decrypt). It has been pointed out to me that https://godoc.org/golang.org/x/crypto/nacl/secretbox is the more canonical solution, but feel free to use this, too.


延迟语句:Go中的保留字,导致函数作为函数返回时发生的最后一件事情执行。您可以根据需要堆叠多达数量,并按相反的顺序执行它们的声明。它看起来像这样:

func myFunction(intArg int, stringArg string) (int, error) {
    networkConn := openNetworkConnection()
    defer networkConn.Close()
    if intArg < 0 {
        errMsg := fmt.Sprintf("myFunction can only deal with positive integers, invalid argument: %v", intArg)
        return 0, errors.New(errMsg) // right after this, your deferred networkConn.Close() will execute
    }
    return intArg, nil // right after this, your deferred networkConn.Close() will execute
}

但是,有一个复杂的问题。如果您在延迟语句中命名的函数使用参数,如果这些参数是其他函数的返回值,则在声明延迟时执行参数函数,并且延迟函数本身在函数exit执行:

func myFunction(intArg int, stringArg string) (int, error) {
    networkConn := openNetworkConnection()
    //determineHowConnShouldBeClosed() executes NOW, its return-value is stored as an
    // argument to closeMyNetworkConnection when it finally gets run.
    defer closeMyNetworkConnection(networkConn, determineHowConnShouldBeClosed())
    if intArg < 0 {
        errMsg := fmt.Sprintf("myFunction can only deal with positive integers, invalid argument: %v", intArg)
        return 0, errors.New(errMsg) // right after this, your deferred networkConn.Close() will execute
    }
    return intArg, nil // right after this, your deferred networkConn.Close() will execute
}
如果您的os.Exit()或log.Fatal()显式地:

,则延迟函数将永远不会被执行
func main() {
  defer func() {
    fmt.Println("Hello World!") // The anonymous func will not be executed
  }()
  os.Exit(1)
}

接口总是指针:请考虑以下示例:

package main

import ( "fmt" )

type Fooer interface { Foo() int }

type ImplementsFooer struct{ DataMember int }

// provide the function foo to make your ImplementsFooer struct // implement Fooer. Note that this function has a non-pointer // receiver to an ImplementsFooer instance, we'll get to that // later func (f ImplementsFooer) Foo() int { return f.DataMember }

func WantsPointerToFooer(f *Fooer) { fmt.Printf("I got a fooer, result of Foo() is %v\n", f.Foo()) }

func main(){ var myInstance Fooer // declare as type Fooer myInstance = ImplementsFooer{DataMember: 5} WantsPointerToFooer(&myInstance) }

它不编译:* prog.go:24:f.Foo undefined(type Fooer是指向接口的指针,而不是接口)这是因为 myInstance 是类型接口,它已经是一个指针。 应该实际接受(f Fooer),而您仍然会获得正在寻找的传递参考性能。


指针接收器,非指针接收器和接口的实现

当您通过一些第一个go项目进行编程时,您可能会像我一样发现可以向结构体添加实例方法。这样,你可以得到一些类似东西的东西。你可以在python中这样做:

class Foo:
    data_member = 1
    def increment_data_member(self):
        self.data_member += 1

并在Go中看起来像这样:

type Foo struct {
    DataMember int
}
func (f *Foo) IncrementDataMember() {
    f.DataMember++
}
请注意,IncrementDataMember的方法是使用emc函数和增量数据成员之间的接收者作为Foo结构体的部分?那个接收器是一个指针 - 接收器,如果你像我一样,你只是内在化,就像事情一样,但它会更深入。

指针和基本类型,它们指向Go中具有不同的方法集,这会影响实现接口的方式。我们在前面的例子中使用我们实现的 Fooer 接口:

type Fooer interface {
  Foo() int
}

type ImplementsFooer struct{ DataMember int }

// this receiver is NOT a pointer func (f ImplementsFooer) Foo() int { return f.DataMember }

在Go中,

ImplementsFooer 和指向该类型的指针, * ImplementsFooer ,而在本示例中,只有其中一个实现 Fooer 界面。

鉴于这些信息,请查看此示例程序:

package main
import "fmt"

type Fooer interface { Foo() int }

type ImplementsFooer struct{ DataMember int }

// implement the Fooer interface func (f *ImplementsFooer) Foo() int { return f.DataMember }

func main() { implFoo := ImplementsFooer{35} printFoo(implFoo) }

// this function requires a // type that implements Fooer func printFoo(f Fooer) { fmt.Println(f.Foo()) }

此程序将不会编译。当您尝试编译它时,编译器会给您一个错误:

cannot use implFoo (type ImplementsFooer) as type Fooer in argument to printFoo:
  ImplementsFooer does not implement Fooer (Foo method has pointer receiver)

在这种情况下, * ImplementsFooer 类型实现了接口,但是 ImplementsFooer 类型不是 - 结构体,并且指向该结构体的指针有两个不同的方法集去。

该示例程序的简单修复是将指针传递到 printFoo 函数中,如下所示:

func main() {
  implFoo := ImplementsFooer{35}
  printFoo(&implFoo)
}

:=符号:它不是一个表情符号,它是一个自动推断类型的赋值运算符。这样:

var foo int
foo = 5

相当于:

foo := 5

,但如果不小心,可能会使您的范围变得混乱:

foo, err := someFunction() // in this case, err is nil
if true {
    bar, err := someOtherFunction() // in this case, err is not nil, but it will
                                        // go out of scope at the end of the block
}
// out here, we're dealing with the original (nil) declaration of err
if err != nil {
    fmt.Printf("we had an error")
}

(这不打印)

如果你发现自己写这样的东西,你可能意味着这样:

foo, err := someFunction() // err is nil
if true {
    var bar int
    bar, err = someOtherFunction() //err is not nill
}
if err != nil {
    fmt.Printf("we had an error")
}

(这个打印我们有错误)


指针指针永远是新开发人员的绊脚石,而Go也不例外。我建议您不要重新发明该博客,而是阅读: https://www.golang-book。 com / books / intro / 8


Go编译器对于您可能使用的其他语言来说比其他语言更加挑剔:我在这里没有太多具体的建议,但是当我开始时,它会让我发生错误。如果你是在下一行喜欢你的大括号({)的人,那就是一个编译错误,你只需要学习如何生活。幸运的是,有一个名为fmt的自动格式化程序可以为您修复所有内容。在命令行中,运行: go fmt ,它将修复所有go文件中的格式。


相同的行语句是可能的,但是由于范围的原因,它们可能会令人困惑:Go允许您将多个语句放在由分号分隔的同一行上。我没有经常看到这个,但有几种情况我在做。您可以使用它来检查地图中是否存在某些内容:

myMap := make(map[string]string)
myMap["foo"] = "bar"
if value, ok := myMap["baz"]; !ok {
    // the "truthiness" of this if statement is in the last (right-most) statement
    fmt.Printf("baz does not seem to be in myMap\n")
} else {
    fmt.Printf("we found baz, its value is %v\n", value)
}
// be aware that value is not defined in this scope, outside the if/else block

这将检查一个操作是否返回错误:

if result, err := someFunction(); err != nil {
    fmt.Printf("had a problem running someFunction. It returned a value of %v and an error, %v\n", result, err.Error())
}
// be aware that because result is declared inside an if-statement, it is not in scope outside the if-block.

Postfix Increments: They're statements, not operators as they are in other languages. fmt.Printf("%v\n", foo++) doesn't work.


范围运算符不能完全符合我的预期:它似乎给你一个奇怪的准指针,而不是一个真正的参考。举个例子:

package main

import ( "fmt" )

func main() { mySlice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} myChan := make(chan *int, 10)

<span class="pl-k">for</span> <span class="pl-smi">_</span>, <span class="pl-smi">theInt</span> <span class="pl-k">:=</span> <span class="pl-k">range</span> mySlice {
    fmt.<span class="pl-c1">Printf</span>(<span class="pl-s"><span class="pl-pds">&#34;</span>putting a pointer to <span class="pl-c1">%v</span> on the chan<span class="pl-cce">\n</span><span class="pl-pds">&#34;</span></span>, theInt)
    myChan <span class="pl-k">&lt;-</span> &amp;theInt
}

<span class="pl-k">for</span> <span class="pl-smi">i</span> <span class="pl-k">:=</span> <span class="pl-c1">1</span>; i &lt;= <span class="pl-c1">10</span>; i++ {
    <span class="pl-smi">thePtr</span> <span class="pl-k">:=</span> <span class="pl-k">&lt;-</span>myChan
    <span class="pl-k">if</span> *thePtr != i {
        fmt.<span class="pl-c1">Printf</span>(<span class="pl-s"><span class="pl-pds">&#34;</span>pulled <span class="pl-c1">%v</span> off the chan, expected <span class="pl-c1">%v</span><span class="pl-cce">\n</span><span class="pl-pds">&#34;</span></span>, *thePtr, i)
    }
}

}

您可能希望将mySlice切片的每个成员放在通道上,但不会。输出为:

putting a pointer to 1 on the chan
putting a pointer to 2 on the chan
putting a pointer to 3 on the chan
putting a pointer to 4 on the chan
putting a pointer to 5 on the chan
putting a pointer to 6 on the chan
putting a pointer to 7 on the chan
putting a pointer to 8 on the chan
putting a pointer to 9 on the chan
putting a pointer to 10 on the chan
pulled 10 off the chan, expected 1
pulled 10 off the chan, expected 2
pulled 10 off the chan, expected 3
pulled 10 off the chan, expected 4
pulled 10 off the chan, expected 5
pulled 10 off the chan, expected 6
pulled 10 off the chan, expected 7
pulled 10 off the chan, expected 8
pulled 10 off the chan, expected 9
因此,如该范围对象中定义的那样,在for语句的范围内可以安全地使用它,但是指向它的指针似乎稍后改变。因此,对于这样的用例,简单地迭代:

更安全
for i := 0; i < len(mySlice); i++ {
    myChan <- &mySlice[i]
}