Go: It Mostly Doesn't Suck

Adam Thornton

Speaking As A Private Individual

athornton@gmail.com



This presentation: https://goo.gl/uvrSpB

Preliminaries

All software sucks.

I hope to convince you in the next few dozen minutes that Go sucks less than most software.

All hardware also sucks, and all users suck, too, but that's not what this talk is about.

tl;dr

Apologies to Randall Monroe

https://xkcd.com/918/

You Can Leave Now

That was the important part.

Yeah, But What Does That Even Mean?

If Go feels like programming, then...

...C feels like programming naked,

...Python feels like programming with training wheels,

...Perl feels like unicycling naked,

...LISP feels like wielding an elegant weapon for a more civilized age,

...Java feels like programming while wearing mittens,

...COBOL feels like programming while wearing handcuffs and drunk in the back of a police cruiser,

...and PHP feels like punching yourself in the gender-appropriate sensitive spots over and over.

A Slightly Less Pungent Version?

Ways Go is like C (an incomplete list):

But then why not just use C?

Things Go Does Better Than C (an incomplete list):

Enough Theory

Hello, World

package main

import "fmt"

func main() {
  fmt.Println("Hello, world!")
}

Line-by-line

package main

Select a package; "main" is used for commands.

import "fmt"

Packages each have their own namespace; fmt is for text formatting.

func main() {

Just like in C, the primary entry point is called main(). In Go, main() takes no arguments and returns nothing.

  • If you're wondering, os.Argv is where the rest of the command line lives.
fmt.Println("Hello, world!")

Println comes from the fmt package. It is capitalized (we'll see why later), and adds a newline to the end of its arguments.

}

Closes the function (and ends the program).

Packages

Standard library is quite robust: math library (including complex and arbitrary precision numbers ), HTTP, regular expressions, JSON and XML encoding, 2D graphics....

No separate header files: the prologue of a Go binary package contains function names and their signatures.

Imports are done on a per-file basis. That completely obviates the need for

#ifndef _GONKULATOR_H
#define _GONKULATOR_H
/* Code goes here */
#endif /* gonkulator.h */

...and unused imports are a fatal compile-time error.

Packages and Linkers and Stuff

Oh My

Exported functions' names start with an uppercase letter.

Everything is statically linked.

Remote Imports

import (
  "github.com/gonkulator/libkv/store"
)

The dependency fetcher (go get) is smart enough to know that github uses git. It knows about Bitbucket, GitHub, Google Code Project Hosting, Launchpad, and IBM DevOps Services.

You can also define private repositories, using any of Bazaar, Git, Mercurial, or Subversion.

Namespace Collisions

import (
  "crypto/tls"
  aztls "github.com/Azure/azure-sdk-for-go/core/tls"
)

Refer to the standard TLS package as "tls" and Azure's as "aztls".

Package functions are always referred to as package dot function. So you're free to have both:

math.Tan()
salon.Tan()

Dependency Versioning

This is one of the things you're going to hear programming hipsters hate on Go about. They have a point.

As of Go 1.6, the GO15VENDOREXPERIMENT becomes standard, and no longer can be turned off in 1.7.

I didn't like it much.

It just says that if you have a folder in the top level of your project that is named vendor, then you put a tree rooted there with the dependencies you need; these dependencies are never auto-updated with go get.

This seems hinky and ad-hoc. But, on the bright side, it's simple and easy to understand.

Anyone who insists super-stridently about this is blowing smoke and concern trolling you anyway. It's definitely Not That Terrible.

Things You Will Miss

No REPL loop [0].

No optional arguments.

#0:

Yes, I am aware this is like saying "ATM Machine".

Things You Might Miss

Generics.

Preprocessor macros.

Things You Won't Miss

C:

  • Pointer arithmetic.
  • malloc(), free().
  • #ifdef guards.

Java:

  • FactoryDBConnectorFactoryAbstractBeanImplementorEnterpriseSetterFactoryGeneratorFactory()
  • Working in a language that Oracle only resentfully supports.
  • P.S. Larry Ellison hates you.

Python:

  • Needing virtualenv to manage dependency hell.
  • __init.py__ and class directory structure/namespace.
  • Duck typing. Well, I don't miss it. Go Interfaces give me an equivalent with strong compile-time type-checking.

PHP:

  • Everything.

The Best Thing About Go

I'm not sure how to define this crisply, but:

In Go, the gap between having a program that compiles and a program that does what I want it to is consistently much smaller than it is in any other language I've used, and I've used a lot of languages.

If You're Not A Programmer Yet But Would Like To Learn

I think Go would be a pretty good first language.

It would be an even better second language. Python is more approachable and forgiving. But here are some things that make Go a good first language:

  • Other languages may call these things hashes, dicts, or associative arrays. Whatever you call them, they're wonderfully useful.

Basically Imperative

  • You can learn them when you're ready.
  • You can still write perfectly reasonable and idiomatic programs without them first.

Also Nice For The Novice

The built-in github-friendliness and autogeneration of documentation helps to create a particular culture around Go code that is made public.

  • clever and incomprehensible, or
  • prolix, repetitive, and boring.

Unicode Support

There's a unicode package.

Strings are Unicode already. But really they're byte arrays.

Mostly it just works. At least I haven't had to think about it much.

Arrays and Slices

Arrays have a specific fixed length. Slices can grow and shrink. Each one is sequential storage for elements of a particular type.

This is one of the confusing bits of Go, and it's hard to address in a short talk. You get used to it pretty quickly.

Slices support indexing; the index intervals are half-open, like Python:

import "fmt"
//...
l := []string{"a","b","c","d"}
fmt.Printf("%v\n",l[0:2]) // [a b]
fmt.Printf("%v\n",l[:2])  // [a b]
fmt.Printf("%v\n",l[2:4]) // [c d]
fmt.Printf("%v\n",l[2:])  // [c d]
fmt.Printf("%v\n",l[:])   // [a b c d]
// BUT:
// fmt.Printf("%v\n",[:-1]) yields ...
// invalid slice index -1 (index must be non-negative)
// Go isn't Python.
fmt.Printf("%v\n",l[:len(l)-1]) // [a b c]

Maps

Maps: just like Perl hashes or Python dicts.

var m map[string]string
m["foo"] = "bar"
fmt.Printf("%+v\n",m)
m := make(map[string]string)
m["foo"] = "bar"
fmt.Printf("%+v\n",m)

More About Maps

A map key must be a comparable type. A value can be any type.

tl;dr sane map keys are going to work (and many insane keys).

Structs

A lot like C.

type Employee struct {
    Firstname string
    Lastname  string
    Salary    float64 // We have grand ambitions
    Title     string
}

Access fields with a dot.

var e Employee
e.Title="Yes-Man, Third Class"

Embedded Structs

Sort of like an inheritance-by-composition model.

type Name struct {
  Firstname string
  Lastname  string
  Middlename string
  Suffix string
}
type Employee struct {
  Name
  Salary float64
  Title string
}
var e Employee

You can still refer directly to e.Firstname (you can also say e.Name.Firstname)

(https://golang.org/doc/effective_go.html#embedding)

Unit Testing

A little like Perl's test framework.

Run it with go test.

https://golang.org/pkg/testing/

A Little Tour Of Unusual Go Features

There are some things Go does that aren't much like C at all. Here are a few:

Multiple Return Values

This is most commonly seen as:

var err error
var s string
// ...
if somethingWentWrong() {
  return "",fmt.Errorf("something went wrong")
}
return "bob's yer uncle", nil

But you are free to return multiple values of any type:

func WeirdReturner(f float) (int, rune, *map[string][]float, error) {
  ...
}

Goroutines

Go's concurrency support is in the runtime. It uses things called goroutines (from "coroutines"), which are pretty much threads, but don't require OS support.

Channels

Go's channels are a synchronization mechanism. A channel passes a particular type of value.

i := make(chan int)        // Unbuffered
s := make(chan string, 3)  // Capacity of three strings
i <- 1                     // Write to channel
r := <-s                   // Read from channel

Typically you'd use multiple channels in a select loop, which looks just like a select() loop in C or old-school Perl or whatever:

for {
    select {
        case m :<- c1:
            HandleC1(m)
        case m :<- c2:
            HandleC2(m)
        // ....
    }
}

See https://talks.golang.org/2012/waza.slide

Interfaces

This is how you get polymorphism in Go:

Type Declaration

Most of the types you declare will probably be either array or struct types. Like so:

type Userlist []string
type Employee struct {
    Firstname string
    Lastname  string
    Salary    float64 // We have grand ambitions
    Title     string
}

Type Methods

Look just like function definitions, except they have another parameter before the function name.

func (e *Employee) ChangeTitle(title string) string {
    // Needs to be a pointer to Employee because we are modifying it.
    oldtitle := e.Title
    e.Title = title
    return oldtitle
}

Interface Definition

An interface is just a set of type methods that an object must provide.

type Stringer interface {
    String() string
}

The various fmt.Printf variations use an object's String() method, if it exists, to display the textual representation of an object. If it doesn't have one, you just get the list of fields in order. Let's add Stringer to Employee.

Interface Definition Example

import "fmt"
e := Employee{
    Firstname: "Edna",
    Lastname: "Schultz",
    Title: "Director of Something",
    Salary: 91532.20,
}
fmt.Println("Employee: %v\n",e)

Yields: Employee: {Edna Schultz 91532.2 Director of Something}

That's ugly and we don't want to display the salary when we print the object. So let's add a String() method:

func (e Employee) String() string {
    s := e.Lastname + ", " + e.Firstname + " [" + e.Title + "]"
    return s
}

Now we get Employee: Schultz, Edna [Director of Something], which looks a lot better.

defer

defer is the best thing since sliced bread.

When you defer a function, you are saying: when you exit this function, whether normally or via a panic() (we're getting to those next), run the deferred function.

bucket, err := couchbase.GetBucket(Bucketname)
if err != nil {
    // Complain, and then...
    return err
}
// If we got here, we have a bucket.  We want to close it when we exit,
//  however we exit
defer bucket.Close()
// ... do stuff with the bucket
return nil

This makes it ever so much easier to remember to clean up resources when you're done with them.

Errors and Exceptions

Go is not Java. In general, you want to return an error, not throw an exception.

Functions can return multiple values, so a function signature that returns a result and an error is a very common idiom.

An error is a built-in type.

type error interface {
    Error() string
}

So you're free to define your own with more structure if you like (HTTP is a good example).

Using Errors

import "fmt"
func Scarborough(arg string) error {
    switch arg {
        case "parsley", "sage", "rosemary", "thyme"
            return nil
        default:
            return fmt.Errorf("ingredient '%s' not Simon-and-Garfunkel approved",arg)
    }
}

Typical calling convention is:

err := Scarborough(arg)
if err != nil {
    fmt.Printf("Guess *you're* not going to Scarborough Fair: %v",err)
}

Exceptions

Exceptions are exceptional. Errors are not generally exceptional.

panic(s)

A panic in function F does the following:

  1. Stops execution of F.
  2. Executes all of F's deferred functions.
  3. Returns to the caller of F.
  4. Acts as if F had been a call to panic.

Recovering from Panic

recover only works inside a deferred function. It catches the panic value (a string) and returns it.

If a panic reaches the top of a goroutine's call stack, the program exits and prints a stack trace.

The standard library package json contains a good example of this.

In general, you'd only recover a panic inside a library, because you generally want to return an error rather than destroy your caller's program.

Some Random Language Nerd Things

Functions are first-class objects.

You can use anonymous functions to make closures.

Go supports reflection, so you can do type introspection.

Editor Support

There appears to be editor support for the major editors, by which I mean:

I can vouch for Emacs, Atom, and, uh, VSCode. For the most part Go support doesn't ship with the editor and you will have to install a plugin to get it.

Code Style

Brilliant Gordian Knot solution.

Set your editor to display tabs at a width you like, let the editor mode deal with it, and set up the environment to run go fmt on save.

Godoc

https://blog.golang.org/godoc-documenting-go-code

Basically, put a comment immediately before the function, with no intervening space, make sure that it starts with the name of the thing it's describing, and if it is on Bitbucket, GitHub, or Launchpad, then the first time anyone looks for it by import path, the documentation is autogenerated.

Cute Logo

Gopher from golang.org, designed by Renée French, licensed under Creative Commons Attribution 3.0 License.

Oh, And There's This

Google it as golang rather than go or you will be sad.

Larger Example, Depending On Time

Let's write a thing. Who wants to write a what?

This Talk

https://athornton.github.io/go-it-mostly-doesnt-suck (formatted) https://github.com/athornton/go-it-mostly-doesnt-suck.git (source)

Questions?

Not like I have answers. But I'll do my best.

Adam Thornton

athornton@gmail.com

SpaceForward
Left, Down, Page DownNext slide
Right, Up, Page UpPrevious slide
POpen presenter console
HToggle this help