The Oddities of Go (compared to other programming languages)
I’ve been learning Go recently so I thought it’d be useful to write a bit about what has been new for me (and sometimes even strange). Most of my prior experience has been Python, Javascript, C, C++, and Java, so there are some things about Go that are very different from all of those.
For now, I’ll make a skeleton list and fill out each section when I have time.
Hello World in Go
To get you familiar with the syntax of Go, here’s an example program that prints “Hello World!”
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
Packages
Go is organized into packages. Every go file has to belong to a package.
If you’re wondering, this is much less irksome than Java’s requirement that everything belong to a class.
To say what package a Go file is in, you put package <packagename>
at the top.
Every package is confined to a directory, meaning you can’t have .go files with different package names in the same directory.
The main package is called main
, which is the oddest of all.
That’s all I’m going to say for now about packages. Nothing too odd there, but it’s helpful background for the next section.
Private/public
Many languages have some kind of way to specify whether a variable, function, variable, or class
is public or private (or protected, etc).
In C++ and Java, you use key words like private
and public
to make this distinction.
Python typically uses underscores to signify private, or at least you shouldn’t rely on it as part of
its public API, but it’s not enforced by the language.
In Go, “private” things start with a lowercase letter, while “public” things start with uppercase letters.
A package only exports the identifiers (variables, functions, etc) that start with an uppercase letter.
All identifiers declared in a package are available throughout that package, whether they start with upper or lowercase.
Let’s do a little example.
Here are the contents of example1.go
:
package example
var myPrivateThing = "asdf"
var MyPublicThing = "jkl;"
In this case, myPrivateThing
is available throughout package example
.
If I had another file example2.go
, I could access everything defined in example1.go
.
package example
import "fmt"
func printSomething() {
fmt.Println(MyPublicThing) // success
fmt.Println(myPrivateThing) // success
}
MyPublicThing
could be accessed from any package that imports package example
, but myPrivateThing
cannot.
Here’s an example of that in a theoretical main.go
:
package main
import (
"fmt"
"some/path/to/example"
)
func main() {
fmt.Println(example.MyPublicThing) // succeeds
fmt.Println(example.myPrivateThing) // error
}
Declaring variables
When you declare a variable in C or C++, it might look something like this:
int i = 1;
In Go, you can achieve the same like so:
var i = 1
In this case, the Go compiler is smart enough to figure out that i
is an int
.
There are a few other ways to declare variables in Go.
For example, you can declare the type explicitly:
var i int
and then define it later:
i = 1
There’s also this nice shorthand operator you can use that you’ll likely see more often than not in Go source code:
i := 1
This means something like declare the variable *i*, and make it equal to 1.
Semi-colons?
Unlike C, C++, and Java, you almost never need to use semi-colons in Go (but you can). Technically, Go uses semi-colons to terminate statements, but the lexer inserts most of them for you. This takes some getting used to. Semi-colons are probably the thing I mess up the most often when switching between languages.
Function signatures
The signature of a function is basically what you would include in a C or C++ header file.
It defines the return type of the function (void
, int
, char *
, etc), the function’s parameters (if any), and the name of the function.
For example, here’s a C function that tells you if one number is divisible by another number:
int isDivisibleBy(int dividend, int divisor)
{
return dividend % divisor == 0;
}
In Go, it would look like this:
func isDivisibleBy(dividend int, divisor int) int {
return dividend % divisor == 0
}
A few differences to note:
- When you write a function in Go, you use the word
func
. - The types of the parameters come after their names, whereas in C the types come before the names
- The return type of the function comes after the parameter list, and before the open curly brace.
The above function could also be written like this:
func isDivisibleBy(dividend, divisor int) int {
return dividend % divisor == 0
}
Notice this time I didn’t include the first int
. If you don’t include the type of a parameter, it will take the type of the next parameter to the right that has a type.
So if you have 100 parameters and they’re all of type string
, you only have to write string
for the very last (rightmost) one.
Naked returns and named return parameters
This is one of the oddest parts of Go syntax (or coolest, depending on your point of view).
Take the following function in Go:
// Max takes a slice (a.k.a. a list) of ints, and returns the one with the largest value
// If the slice is empty, it will return an error
func Max(numbers []int) (int, error) {
var max int
if len(numbers) == 0 {
return max, errors.Error("Slice is empty!")
}
max = numbers[0]
for _, n := range numbers[0:] {
if n > max {
max = n
}
}
return max, nil
}
As the comment says, this is a simple (naive) function to get the max value of a slice (a slice is kind of like an array or a list).
It has multiple return values: an int
, and the other is Go’s builtin error
type.
Multiple return values in Go is very common.
It’s also very common for one of them to be error
.
To illustrate what I mean by “naked returns” I’m going to rewrite the function slightly:
func Max(numbers []int) (int, error) {
var max int
var err error
if len(numbers) == 0 {
err = errors.Error("Slice is empty!")
return
}
max = numbers[0]
for _, n := range numbers[0:] {
if n > max {
max = n
}
}
return
}
You’ll notice I changed three lines:
- I added the line that declared
err
(var err error
)
… Oops, out of time. I’ll finish this section later this week.