Gripes With Go
You’ve read a lot of posts about the shortcomings of the Go programming language, so what’s one more.
- Lack of sum types
- Type assertions
- Date and Time
- Statements over Expressions
- Erroring out on unused variables
- Error handling
Lack of Sum types
A “Sum” type is a data type that can hold one of many states at a given time, similar to how a boolean can hold a true or a false, not too different from an enum
type in C. Go lacks enum
types unfortunately, and you are forced to resort to crafting your own substitute.
A type to represent gender for example:
type Gender int
const (
Male Gender = iota // assigns Male to 0
Female // assigns Female to 1
Other // assigns Other to 2
)
fmt.Println("My gender is ", Male)
// My gender is 0
// Oops! We have to implement String() for Gender ...
func (g Gender) String() string {
switch (g) {
case 0: return "Male"
case 1: return "Female"
default: return "Other"
}
}
// You can accidentally do stupid stuff like:
gender := Male + 1
The Haskell equivalent of the same:
Type Assertions
A downcast with an optional error check? What could go wrong?
Type assertions in Go allow you to do:
The error check however is optional:
var x interface{} = 7
var y := x.(float64)
fmt.Println(y)
// results in a runtime error:
// panic: interface conversion: interface {} is int, not float64
Date and Time
Anyone that has written Go previously, will probably already know what I am getting at here. For the uninitiated, parsing and formatting dates in Go requires a “layout”. This “layout” is based on magical reference date:
Mon Jan 2 15:04:05 MST 2006
Which is the date produced when you write the first seven natural numbers like so:
01/02 03:04:05 '06 -0700
Parsing a string in YYYY-MM-DD
format would look something like:
This so-called “intuitive” method of formatting dates doesn’t allow you to print 0000 hrs
as 2400 hrs
, it doesn’t allow you to omit the leading zero in 24 hour formats. It is rife with inconveniences, if only there were a tried and tested date formatting convention …
Statements over Expressions
Statements have side effects, expressions return values. More often than not, expressions are easier to understand at a glance: evaluate the LHS and assign the same to the RHS.
Rust allows you to create local namespaces, and treats blocks ({}
) as expressions:
The Go equivalent of the same:
Erroring out on unused variables
Want to quickly prototype something? Go says no! In all seriousness, a warning would suffice, I don’t want to have to go back and comment each unused import out, only to come back and uncomment them a few seconds later.
Error handling
Need I say more? I will, for good measure:
- Error handling is optional
- Errors are propagated via a clunky
if
+return
statement
I prefer Haskell’s “Monadic” error handling, which is employed by Rust as well:
// 1. error handling is compulsory
// 2. errors are propagated with the `?` operator
fn foo() -> Result<String, io::Error> {
let mut f = File::open("foo.txt")?; // return if error
let mut s = String::new();
f.read_to_string(&mut s)?; // return if error
Ok(s) // all good, return a string inside a `Result` context
}
fn main() {
// `contents` is an enum known as Result:
let contents = foo();
match contents {
Ok(c) => println!(c),
Err(e) => eprintln!(e)
}
}
Conclusion
I did not want to conclude without talking about stylistic choices, lack of metaprogramming, bizzare export rules, but, I am too busy converting my interface{}
types into actual generic code for Go v2.
I'm Akshay, I go by nerd or nerdypepper on the internet.
I am a compsci undergrad, Rust programmer and an enthusiastic Vimmer. I write open-source stuff to pass time. I also design fonts: scientifica, curie.
Send me a mail at nerdy@peppe.rs or a message at nerdypepper@irc.rizon.net.