• This repository has been archived on 05/Apr/2022
  • Stars
    star
    263
  • Rank 155,624 (Top 4 %)
  • Language
    Go
  • License
    MIT License
  • Created over 10 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Go source transformation tool for generics

gengen - A generics code generator for Go

๐ŸŽ‰๐ŸŽ‰ Go 1.18 includes native support for generics! ๐ŸŽ‰๐ŸŽ‰


People often lament the lack of generics in Go, and use it as an excuse to dismiss the language. Yes, it is annoying that you often end up rewriting boilerplate. And yes, it is annoying that it's not possible to write a generic data structure that can be type-checked at compile time.

However, we can use Go's powerful source parsing and AST representation packages to build a program that can translate generically-defined code into specifically typed source code and compile that into our projects.

How to use it

Install the gengen tool:

$ go install github.com/joeshaw/gengen@latest

Create a Go package with a generic implementation. For example, this contrived linked-list implementation in list.go, which lives in the github.com/joeshaw/gengen/examples/list package:

package list

import "github.com/joeshaw/gengen/generic"

type List struct {
    data generic.T
    next *List
}

func (l *List) Prepend(d generic.T) *List {
    n := &List{
        data: d,
        next: l,
    }

    return n
}

func (l *List) Contains(d generic.T) bool {
    if l == nil {
        return false
    }

    for i := l; i != nil; i = i.next {
        if i.data == d {
            return true
        }
    }

    return false
}

func (l *List) Data() generic.T {
    if l == nil {
        // Return the zero value for generic.T, whatever type it ends
        // up becoming
        var zero generic.T
        return zero
    }

    return l.data
}

generic.T is simply interface{}. This list implementation is perfectly valid Go code and you could use it as-is, asserting types at runtime.

However, you can generate a specifically typed version of this file by running it through gengen:

$ gengen github.com/joeshaw/gengen/examples/list string

This will generate a list.go that looks like this:

package list

type List struct {
    data string
    next *List
}

func (l *List) Prepend(d string) *List {
    n := &List{
        data: d,
        next: l,
    }

    return n
}

func (l *List) Contains(d string) bool {
    if l == nil {
        return false
    }

    for i := l; i != nil; i = i.next {
        if i.data == d {
            return true
        }
    }

    return false
}

func (l *List) Data() string {
    if l == nil {
        // Return the zero value for generic.T, whatever type it ends
        // up becoming (in this example, string)
        var zero string
        return zero
    }

    return l.data
}

The generic package also defines generic.U and generic.V as additional generic types for cases when you want to support more than one type. Simply pass the additional types on the gengen command line:

$ gengen github.com/joeshaw/gengen/examples/btree int string

Lastly, you can use gengen in conjunction with go generate. For example:

//go:generate gengen -o ./btree github.com/joeshaw/gengen/examples/btree string int

Caveats

Number of generic types

Currently gengen can support up to three generic types: generic.T, generic.U, and generic.V.

Package Naming

gengen does not currently do anything with naming of packages or types. If you want to import multiple copies of a package (either generic or typed) you will need to rename the package at import time. For example, after generating a typed btree into github.com/example/btree:

import "github.com/example/btree"
import gen_btree "github.com/joeshaw/gengen/examples/btree"

Using zero values

You may need to write code in a slightly different way than you normally would for interface{} in order to support a wide range of types. For instance, in our Data() method, note that we cannot simply return nil in the l == nil case because nil is not a valid value for primitive types like int, string, etc. Instead we instantiate a variable of our generic type but do not assign to it, ensuring that we always return the zero value for that type.

Equality

Checking for equality in a generic implementation can be tricky, and blindly checking if x == y often will not work as you'd hope. For things like slices, it will not even compile. If you need to check for equality, you might want to create an Equaler interface, like so:

type Equaler interface {
    Equal(other Equaler) bool
}

Define types that implement this interface:

type intWithEqual int

func (i intWithEqual) Equal(other Equaler) bool {
    if i2, ok := other.(intWithEqual); ok {
        return i == i2
    }
    return false
}
type Person struct {
    Name string
    SSN string
}

func (p *Person) Equal(other Equaler) bool {
    if p2, ok := other.(*Person); ok {
        return p.SSN == p2.SSN
    }
    return false
}

In your generic implementation, use the interface rather than comparing directly:

type MySlice []generic.T

func (s MySlice) Contains(e generic.T) bool {
    for _, e2 := range s {
        if e2.Equal(e) {
            return true
        }
    }
    return false
}

(Note that because generic.T does not embed the Equaler interface, this code won't compile without being run through gengen first.)

Finally, generate your implementations:

$ gengen myslice.go intWithEqual > myslice_int.go
$ gengen myslice.go *Person > myslice_person.go

Import and type naming inflexibility

The gengen tool looks through the source code for specific strings in order to replace them in the AST. Specifically, it looks for the import github.com/joeshaw/gengen/generic and the types generic.T, generic.U, and generic.V. If you need to change these, you will also have to change the gengen.go source.

Origins

I had been mulling the idea of a generics generator for a while, originally planning to use the text/template package. However, during a panel discussion at GopherCon in which generics inevitably came up, Rob Pike suggested manipulating the AST for Go. I began implementing this approach during the GopherCon Hack Day on 26 April 2014.

More Repositories

1

envdecode

Go package for populating structs from environment variables using struct tags
Go
222
star
2

xserver

(An outdated mirror and fork of) The X.org server
C
35
star
3

carwings

Go package and CLI tool for the Nissan Leaf Carwings API
Go
31
star
4

cuckoofilter

An implementation of Cuckoo Filters in Go
Go
28
star
5

multierror

Go package for encapsulating multiple errors
Go
27
star
6

mbta-bus

Real-time tracking of MBTA buses on Google Maps
JavaScript
23
star
7

iso8601

Go package for encoding time in JSON in ISO 8601 format
Go
22
star
8

powerley-energybridge-homekit

HomeKit support and Prometheus exporter for the Powerley Energy Bridge (aka AEP Energy Bridge or DTE Energy Bridge)
Go
18
star
9

json-lossless

Lossless JSON encoding/decoding package in Go
Go
17
star
10

myq

Go package and CLI tool for the Chamberlain / LiftMaster MyQ API
Go
12
star
11

cipherlist

Example code from https://www.joeshaw.org/abusing-go-linkname-to-customize-tls13-cipher-suites/
Go
9
star
12

myq-homekit

HomeKit support for Chamberlain / LiftMaster MyQ
Go
8
star
13

banshee-web

A web interface to the Banshee music library
7
star
14

nstats

Command-line tool for printing simple numerical statistics
Go
6
star
15

doc-extract

Go tool for extracting text from specially tagged Go comments
HTML
6
star
16

leaf

Go package and CLI tool for the Nissan Leaf North American NissanConnect API
Go
6
star
17

kasa-homekit

HomeKit support for TP-Link Kasa smart home devices using HomeControl
Go
5
star
18

beagle

A desktop indexing and search system for Linux
C#
5
star
19

ecp-ja3

Fastly Compute service to display JA3 fingerprints
Go
5
star
20

tripplite-ups-exporter

Prometheus exporter for Tripp Lite UPSes over USB HID
Go
5
star
21

rst-extract

[deprecated] Tool for extracting ReStructured Text (RST) from specially tagged Go comments
Go
5
star
22

carwings-homekit

HomeKit support for Nissan Leaf
Go
5
star
23

wemo-homekit

HomeKit support for Belkin Wemo devices in Go
Go
4
star
24

hal

A hardware discovery and enumeration system for Linux
C
3
star
25

gjs

gjs with a debugger implementation
C
3
star
26

pgactivity

A tool to print out pg_stat_activity on an interval
Go
3
star
27

gchat-jetpack-notifier

A notifier for Gmail's embedded chat for Mozilla Labs Jetpack
JavaScript
3
star
28

pubnub-python

A fork of https://github.com/pubnub/pubnub-api/tree/master/python, so that it can be installed via pip
Python
3
star
29

pulseaudio

personal repo for pulseaudio patches
C
2
star
30

customerio

A Go package for the customer.io email service
Go
2
star
31

rug

A command-line interface to rcd
Python
2
star
32

adguardhome-homekit

HomeKit support for AdGuard Home
Go
2
star
33

talks

Slides from talks I've given
Go
2
star
34

xf86-input-synaptics

synaptics input driver for X.org
C
2
star
35

thermistor

Using a Raspberry Pi and ADS1015 I2C ADC to read temperatures from thermistors and report them to HomeKit and Prometheus
Go
2
star
36

busybox-nonroot-docker

Docker image based on busybox:glibc with "nobody" user with UID 1.
2
star
37

iata

CLI tool to print info on airports given IATA or ICAO codes
Go
2
star
38

leaf-homekit

HomeKit support for Nissan Leaf using HomeControl
Go
2
star
39

podfeed

The quickest and dirtiest of podcast feed generators
Go
1
star
40

vagrant-puppet

Vagrant configuration for testing a git-hosted puppet config on ubuntu lucid
Ruby
1
star
41

advent2022

Advent of Code 2022. Using ChatGPT to generate solutions.
Go
1
star
42

iridium-phonebook

CLI tool for managing your Iridium 9555 satellite phone's addressbook
Go
1
star
43

rcd

Red Carpet Daemon
C
1
star
44

red-carpet

A GTK GUI to rcd
Python
1
star
45

roku-homekit

HomeKit support for Roku devices with the External Control Protocol
Go
1
star
46

libredcarpet

Package management library for RPM and DPKG based systems.
C
1
star
47

peercred

A wrapper around using Linux's SO_PEERCRED socket option on Unix domain sockets
Go
1
star
48

slack-handles

Chrome extension to display Slack handles alongside real names https://chrome.google.com/webstore/detail/slack-real-names-%2B-handle/hlebmnlokgglggfmafocplopinfpkiad
JavaScript
1
star