Experimenting with Go Type Parameters (Generics) in GoLand
In today’s article, we will experiment with generics in Go, and their latest form, Type Parameters.
Before we start, let’s take a quick look at the proposal’s history.
History of generics in Go
Generics have been some of the most desired language features since Go’s inception. While the Go team never explicitly rejected them, they also did not have a good way to solve the problem of introducing generics to the language without making Go look and feel less Go-like.
That was until August 2018, when the Go team introduced the notion of Contracts and started a big discussion with the community about how generics could look and work in Go.
Based on overwhelming feedback from the community, the Contracts proposal evolved and was modified until mid-2019. After a careful analysis of the proposal, it was determined that contracts are too similar to interfaces. So Contracts were dropped in favor of interfaces with a bit of twist, which we’ll return to later. Instead, a new proposal was developed that simplified the learning curve and usage of Type Parameters in Go.
Another year has passed since those last discussions of Type Parameters, and this year’s GopherCon US provided a better insight into how generic code could look and work in Go.
Enough with the history for now. Let’s start writing some generic code for Go!
Using the Generics Go SDK in GoLand
Before we can start coding, we’re going to need a special version of Go, and we’ll have to compile it ourselves.
For the purposes of this article, I’m using GoLand 2020.3.
Let’s use the Get from VCS action and select the Go repository https://go.googlesource.com/go. We clone that into a folder anywhere on our computer outside of the GOPATH and give the folder a name: go-mainline.
Since we are compiling Go ourselves, we need to perform a few more steps than we would for a regular project.
First, we’ll use the Branches feature, Ctrl+Shift+`, search for the dev.go2go branch, then we select the Checkout option.
Next, we’ll configure some environment variables and settings for the IDE:
- Under Settings/Preferences | Go | GOROOT, select the
<No SDK>
option. - Under Settings/Preferences | Tools | Terminal, add the following environment variables:
GOROOT_BOOTSTRAP=<path on your disk where a Go SDK newer than Go 1.14 is available>
;CGO_ENABLED=0
Note: In this example, we are disabling CGO before building Go. which means that you will not be able to use CGO and test Type Parameters. If you need CGO, omit the CGO_ENABLED=0
variable.
With this step complete, we can open the built-in Terminal of the IDE with Alt + F12 on Windows/Linux or Option + F12 on macOS and navigate to the src folder using cd src
.
Finally, let’s build Go itself by invoking the make.bat command on Windows or make.sh on Linux/macOS.
Once this process is done, we will have a Go version capable of interpreting the Go 2 Type Parameters proposal.
Writing a generics-based Hello World example
Let’s create a Go modules project, using the SDK we just compiled as the project’s Go SDK and a new main.go file.
For the demonstration code here, we’ll use the “best-in-class” sorting algorithm, Bubble Sort, to sort some books by their price.
package main
import "fmt"
type Book struct {
Name string
Price int
}
func (x Book) Less(y Book) bool {
return y.Price < x.Price
}
type Lesser[T any] interface {
Less(y T) bool
}
func doSort[T Lesser[T]](a []T) {
for i := 0; i < len(a)-1; i++ {
for j := i; j < len(a); j++ {
if a[i].Less(a[j]) {
a[i], a[j] = a[j], a[i]
}
}
}
}
func main() {
a := []Book{
{"Second Book", 2},
{"First Book", 1},
{"Fifth Book", 5},
{"Fourth Book", 4},
{"Sixth Book", 6},
{"Third Book", 3},
}
doSort(a)
fmt.Println(a)
}
You’ll immediately notice that the IDE prompts you to rename the file as a .go2
file, so let’s do that.
Running generics code in GoLand
At the moment, GoLand does not support running generics code based on the .go2
file format. So, we’ll have to configure an External Tool to make this work.
Head over to Settings/Preferences | Tools | External Tools and add a new tool with the name: go2go. For the description, we can use something like Run the go2go experimental translation tool for Go Generics. For the Program, use $GOROOT$/bin/go.exe if you are on Windows or $GOROOT/bin/go if you are on Linux/macOS, and for Arguments, use tool go2go build. Finally, under the Working directory, use the $FileDir$ macro and save the tool.
Now let’s run the tool using Search Everywhere, Shift + Shift. This will compile our application and provide us with a binary file and another .go
file. That file is not useful in this case and can be safely ignored.
To run our application, invoke the Terminal using Alt + F12 on Windows/Linux or Option + F12 on macOS and see the results!
Pro tip: Once you compile the binary for the first time, you will see the executable name created by the go2go tool, which allows you to create another External Tool that makes it easier to run.
Pro tip: You can assign shortcuts to External Tools, just like you would for standard IDE features. Search for the tool name in the IDE Settings/Preferences | Keymap and give it the shortcut you want.
Share your code with the world
If you’d like to share your example with other Go programmers, you can do so directly from the IDE.
Invoke the Share in Playground feature using Ctrl + Alt + Shift + S on Windows/Linux or CMD + Option + Shift + S on macOS, and the IDE will share it automatically in Playground. Here’s my shared application: https://go2goplay.golang.org/p/kvXKTMX_Ye0.
Note: Please keep in mind that all the code in the Playground is public, and anyone with the link can access it! Be careful with what you share there.
Conclusion
And that’s it. We just wrote our first generics-enabled Go code.
This feels pretty awesome, right?
Despite still being in the proposal state, we can use Type Parameters to solve some problems that would otherwise be more complex. Fortunately, thanks to the compiler’s Type Inference feature, using Type Parameters does not feel strange, which was one of the Go team’s goals.
However, there are also some important things to keep in mind.
As of this article’s publication, the proposal to introduce generics in Go has not been accepted. Just as we saw a transition from Contracts to Interfaces for Type Parameters, the proposed solution may very well change again in the future, though this seems unlikely.
So far, the editor’s support for generics is still in its early stages. We are working on improving it, but you may find that things don’t always work as expected. A few notable examples are the lack of inspections, quick-fixes, and refactorings. Completion also lags behind what you may be used to.
Keep an eye on our blog, where we’ll publish news on the latest developments concerning this and other topics. If you want to get a peek at our latest changes, you can use the nightly builds during our EAP process. To learn more about how the EAP works, click here.
If you have any questions or feedback about this or our product, let us know in the comments section below or on our issue tracker, or you can tweet to us @GoLandIDE.