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 (
= iota // assigns Male to 0
Male Gender // assigns Female to 1
Female // assigns Other to 2
Other )
.Println("My gender is ", Male)
fmt// 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:
:= Male + 1 gender
The Haskell equivalent of the same:
data Gender = Male
| Female
| Other
deriving (Show)
print $ "My gender is " ++ (show Male)
Type Assertions
A downcast with an optional error check? What could go wrong?
Type assertions in Go allow you to do:
var x interface{} = 7
, goodToGo := x.(int)
yif goodToGo {
.Println(y)
fmt}
The error check however is optional:
var x interface{} = 7
var y := x.(float64)
.Println(y)
fmt// 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:
const layout = "2006-01-02"
.Parse(layout, "2020-08-01") time
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:
let twenty_seven = {
let three = 1 + 2;
let nine = three * three;
* three
nine };
The Go equivalent of the same:
:= nil
twenty_seven
:= 1 + 2
three := three * three
nine = nine * three twenty_seven
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
if err != nil { ... }
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();
.read_to_string(&mut s)?; // return if error
f
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, programmer and pixel-artist. I write open-source stuff. I also design fonts: scientifica, curie.
Reach out at oppili@irc.rizon.net.