Go
Designed to be quick to compile, easy to static analyze, and to have a simple type system (no inheritance hierarchy or polymorphism).
Built by Google, Go reflects their particular environment (lots of computers, tonnes of data).
It's also written in reaction to C++ and its flaws.
There's also something special about the dependencies? Not sure.
Has garbage collection. Compared to Java, has a focus on reducing memory allocations and re-using data structures to reduce the amount of garbage to collect.
Has a focus on decent concurrency.
Generally deficient in semicolons;.
Imports and Packages
Declared your package as package packagename
.
Import one using import "packagename"
.
Packages use a Java style namespacing directory hierarchy. However, you don't have to write it all out in your package packagename
statement, just the last part.
The main package is your entry point, and should contain a function also called main.
To export things from a package, you need to declare them in the highest scope with a capital letter.
Unused imports are a compile error in Go. So are circular dependencies.
Variable Declarations
Declare variables with var nameA, nameB Type = otherThingA, otherThingB
.
You can omit the types if you have something on the right hand side: var nameA, nameB = otherThingA, otherThingB
.
You can also omit var entirely and write := instead of =, but only inside a function.
Assign to nothing using _. For example: var _ I = T{}
is useful for checking whether a type matches an interface.
If you leave off the right hand side initializer, then your variables will get a default zero value.
nameA := Type()
Constants
Use the const keyword instead. They can't take the := syntax.
Number constants have some magic to do with precision. They'll get the best precision possible at the time you use them.
Types
Instead of inheritance, use composition (the language create argue that inheritance encourages early overdesign).
Types automatically fulfill interfaces if their method signatures match. You don't have to declare this.
Interfaces allow dynamic dispatch.
The only polymorphism in Go is to automatically dereference pointers when you call functions for which that would make the signatures match.
Included Types
bool, string, some number types (signed and unsigned; int, float and complex; different sizes), and runes.
A rune is a unicode code point. It effectively replaced char. Hooray, good choice.
Type Conversions
Type(value)
casts value to Type. All type conversions are explicit - missing type conversions are errors.
Type Aliases
For example type MyInt uint8
.
Pointers
Pointers have type *SomeType. Get them using & and dereference them using *, just like in C.
You can't do any arithmetic on pointers in Go though.
The purpose of pointers in Go is to let a function mutate its arguments.
Pointers can also be useful to avoid copying large structs. By default, calling a function makes a copy.
Structs
Define a struct:
type SomeStructType struct { name SomeOtherType name2 SomeOtherType }
To initialize a struct, pass it values for its fields in order SomeStructType{0, 0}
.
Alternatively, you can name the fields SomeStructType{name: 0}
.
Omitted fields will get the default zero value.
Then you can just access them with ..
Arrays
Designed to be fast, but a bit more flexible than C.
Make an array by putting [arraySize] before a type, then write the initializer in braces {}. For example: [3]int{0, 1, 2}
.
You can slice arrays using [] and :, for example: arr[start:end]
. Slices are views onto arrays. You can modify the original through a slice though! Danger, Will Robinson!
You can leave out the start or end of a slice to get all of it in that direction.
If, when constructing an array, you omit the array size, then you will get a slice of that array instead.
You can re-slice a slice. As long as the underlying array is big enough, this is fine.
To see how long an array slice is, use the len function. To see how long its underlying array is, use the cap function.
You can use append to add elements to an array.
Nil
nil means an array slice with no backing array and no start or end.
Alternatively, it means a map with no keys and no backing array.
Maps
Are built in, because it's the new millenium after all.
Make them as:
map[keyType]valueType{ key: value, key: value, }
There's some special nice syntax where, if you're constructing types as your values, you can leave out their constructor name.
You insert things like most languages: m[key] = value
.
Use delete: delete(m, key)
.
You retrieve elements and check for their presence with a two value assignment: value, hasKey = m[key]
. If a value isn't present in the map, it's get the default zero value.
TODO Make
The make
function does something to do with dynamic arrays. I need to read the article.
You can also use it make a map: make(map[keyType]ValueType)
. Not sure why you'd do this.
Functions
Declare with func. Types go after names. Parameter list goes before return list.
func name(paramA, paramB paramABType, paramC paramCType) (returnA, returnB returnType) { return this, that }
The name of a function is optional. You can define them inside other functions.
The type of a function is fn. You can pass them to other functions.
Methods
You can associate a function with a type: func (typeName Type) funcName (argName ArgType) (returnName ReturnType) {}
.
This provides a dot syntax for invoking it on the type: Type{}.funcName(arg)
.
You can only define types in the package where that type was defined.
If a method takes a pointer type (called a pointer receiver), then it can modify the thing it was originally passed.
Pointer receivers can also dispatch on the value (dereferenced) version.
Interfaces
Interfaces declare some methods.
A type meets an interface which it has the methods for.
The empty interface type interface can hold any value.
You can get the original value back out of an interface variable: var t, isT = i.(T)
.
You can also switch on type. In each of the case statements, your variable will automatically get case to that type:
switch v := i.(type) { case T: return v default: return v }
Some common interfaces are:
- Stringer
- Error
- io.Reader
- image.Image
Loops
There's just for (no parentheses). It has these semicolon-separated parts:
- Init
- Update
- Termination condition
You can omit any of them.
You can also use the range form. This iterates over an array slice and provides indexes. This is how Go does a foreach loop. It can also loop over maps.
Branching
Go has if. You can put two statements separated by semicolons in the condition. The last one counts.
If you put an extra statement in your if, and it made some variables, then you can also use those variables in the else conditions and blocks.
Go also has switch. It doesn't have fall-through, so you don't need any break statements. It's pretty liberal about what you can use for cases.
You can leave the variable initialization part out. This gives you an alternative syntax for writing a load of if-elseif-elseif statements.
Helper Programs
godoc reads some source code and makes documentation.
gofmt enforces style.
go vet
finds likely problems in your code. For example, if you are closing over a loop variable.
go test
finds tests.
go get
installs a library.
go install
installs a program on your system.
Go programs are statically linked to their libraries.
Testing
https://golang.org/pkg/testing/
Make a file with ending with _test.go. go test
will automatically find them.
Import and use the testing package.
Write functions like:
TestMyThing(t* testing.T) { t.Log("Herp derp.") t.Fail("This test failed."); // or t.ErrorF if you want to format in some parameters. }
Tests in Go continue after failures.
You can output a test coverage report, if you care about such things:
go test -cover -coverprofile=c.out go tool cover -html=c.out -o coverage.html
Error Handling
The Go creators didn't like error handling in most languages, and so decided to invent something else. There are no exceptions or assertions, and no try/catch/finally block.
To indicate an error, use multi return to provide error codes along with return values.
nil means not an error.
Defer
Go provides defer to handle cleanup that always needs to happen regardless of errors.
You provide a function call to defer. That function call goes on a stack. At the end of the function you wrote defer in, Go executes the stack of deferred functions.
The function args are evaluated straightaway.
You can use defer to modify return values - this is commonly used for fiddling with error codes. This is possible because return values in Go have names, and defer happens after the return keyword.
This seems like a good opportunity for dark magic. It's interesting when you have a self-recursive function…
Panic
Since we don't have exceptions, we need some other mechanism for when our program really has to die.
This is the panic keyword. Every function in the stack will execute its defer stack.
The counterpart to panic is recover(). This is only ever useful inside a deferred function. It returns nil if there is nothing to recover from.
Goroutines
A concurrency primitive based on channels Hoare's Communicating Sequential Processes.
These are basically lightweight threads. They have a stack. The Go runtime will moved them between real threads as resources become available.
You can also do concurrency manually and at a lower level. Normally you might not need to.
go someFunctionCall()
starts a goroutine.
Channels
Communicate between gorountines using channels. Make a channel with var ch = make(chan sometype bufferSize)
.
Send a value to channel with ch <- val
. Get one out with:
val, channelOpen := <- ch
Reading a value from a channel blocks it.
A select statement is a bit like a switch statement where you provide multiple possible communication options (reads or writes to channels). It blocks and does the first one which becomes ready. You can provide a default case here, in which case it won't block, but will do the default case immediately if nothing is ready.
close()
does the obvious. You don't have to use it. It may be helpful to communicate something though.
Inside a for loop: range works on a channel.
Workspace
This is a directory that contains all your Go source code, compiled binaries and so on. It's set with the GOPATH environment variable.