Better Go Syntax (igo project)

Everyone knows that Go is a verbose language. One of the key criticism from beginners is that it takes numerous lines of code to do what a few lines could do in their mother language. In many cases, after some initial experimentation, they leave never to be seen again.

Even experienced Go developers occasionally complain about its verbosity. Some don’t even bother complaining because they know precisely what the official guardianship’s answer will be. I’ve seen feature requests on the official Github page ranging from error-handling to for-loop defers either stalled for years or rejected.

In this article, we are going to discuss the implementation of one particular feature in the igo project. The igo project aims to enhance Go syntax just a little — but enough to make a meaningful difference.

Currently, the igo project supports these features:

  • fordefer — for-loop defer statements
  • Address Operator (&) for strings, constants, and functions
  • defer go statement purely for syntactical internal consistency.

This means that you can harness the power and elegance of defer statements from within for-loops. You can also obtain a pointer directly from a string constant without having to create a hideous temporary variable.

To illustrate, the below snippet is valid igo code:

func main() {
    display(&"igo is so convenient")
    for {
row, err := db.Query("SELECT ...")
if err != nil {
panic(err)
}
fordefer row.Close()
}
}
func display(m *string) {
if m == nil {
fmt.Print("no message")
} else {
fmt.Print(*m)
}
}

How does igo work?

Most professional front-end developers use Typescript (which is standard Javascript with extra features). They transpile typescript code to standard Javascript as part of their build process.

The igo project operates along the same vein. The project provides a code formatter and a transpiler which converts *.igo files to standard *.go files which the Go compiler will happily understand.

This approach was chosen because building what would effectively be a competing Go compiler (à la hard-fork) was always going to be hard to maintain, caeteris paribus reduce adoption rates, and have potential licensing issues (I am technically a lawyer and I certainly don’t want to fight Google’s lawyers).

Fordefer

Most Go developers who have programmed in other languages recognize the power of the defer statement. Other modern languages such as Swift have adopted it too after examining its merits.

The problem with the defer statement is that it gets applied prior to the function returning — which is usually what you want. But not always…

Sometimes you want to use it inside a for-loop. This article shows you the issue at hand. Their first solution is to either not use a defer statement — which is not really a solution but rather a recommendation to run away from the problem. Their second solution is to wrap the block inside the for-loop in a closure and change any continue statements into return statements. This solution means you can no longer break out of the loop elegantly. It also makes breaking and continuing out of parent for-loops from nested for-loops substantially messier than it needs to be. It should be noted that the second solution is the recommended approach by the official Go team upon rejecting the proposal way back in 2012.

How does igo solve the problem?

igo introduces a new statement called fordefer. It’s intended usage is the same as defer, except it is used inside for-loops. All deferred function calls are executed prior to the for-loop’s current iteration ‘returning’.

Behind the scenes, it uses the stack package to implement a Last-In-First-Out stack to push function calls to the stack and then pop them in reverse order when its time to unwind (when the for-loop ‘returns’).

When there are nested for-loops, each for-loop will receive its own LIFO stack. Special care is taken to unwind multiple stacks when breaking or continuing higher-level for-loops.

When is it time to unwind?

There are 6 scenarios where the stack needs to be unwound. Prior to:

  • The natural end of the for-loop block
  • return statements
  • breaks
  • continues
  • panics
  • goto (not implemented)

How does the transpiled code look like?

I’m glad you asked.

<main.igo>
package main
import (
"fmt"
)
func main() {
    sum := 0
for i := 1; i < 10; i++ {
fordefer fmt.Println("partial", sum)
sum += i
}
fmt.Println("final", sum)
}

Here is the transpiled file after running igo build main.igo.

<main.go>
package main
import fordefer "github.com/rocketlaunchr/igo/stack"
import (
"fmt"
)
func main() {
    sum := 0
XVlBzgbaiCMRAjW := fordefer.NewStack(true)
defer XVlBzgbaiCMRAjW.Unwind()
for i := 1; i < 10; i++ {
XVlBzgbaiCMRAjW.Add(false, func() {
fmt.Println("partial", sum)
})
sum += i
XVlBzgbaiCMRAjW.Unwind()
}
fmt.Println("final", sum)
}

Here is the output:

partial 1
partial 3
partial 6
partial 10
partial 15
partial 21
partial 28
partial 36
partial 45
final 45

You will see that the fordefer ensures the first partial sum is printed after sum is incremented.

Future Features

The igo project is open to enhancements provided they fulfill one or more of the primary criteria below.

  • Enhance functionality
  • Reduce code verbosity
  • Makes syntax internally consistent

The project is open for maintainers to help guide this process.

Final Notes

If you find the project valuable, feel free to provide field and bug reports. If you want to have a crack at cementing up some of the minor limitations listed in the readme, feel free to submit a pull-request.

Finally, best wishes for 2019.

Project Homepage: igo project