DEV Community

Chris Wendt
Chris Wendt

Posted on • Edited on

Limitations of Go generics

Go does not allow type parameters (generics) in method declarations, and that limits the APIs you can make.

Here's an example when you might encounter that problem and how to work around it:

Say you're building a library to process streams of values, and you start with this type:

// Stream receives In values from upstream and
// sends Out values downstream.
type Stream[In, Out] ...
Enter fullscreen mode Exit fullscreen mode

Ideally, we could write the following generic .connect() method:

//                           vvvvvvv ❌ not allowed by Go!
func (s Stream[A, B]) connect[C any](o Stream[B, C]) ...
Enter fullscreen mode Exit fullscreen mode

But Go does not allow type parameters in method declarations. That means the .connect() method can't introduce any new types:

//                                                  vvvvvv βœ… OK
func (s Stream[A, B]) connect(o Stream[B, B]) Stream[A, B]
Enter fullscreen mode Exit fullscreen mode

That restricts the kinds of processing we can do by chaining .connect() methods together:

lines
    .connect(omitEmpty) // βœ… (string -> string) same type
    .connect(getLength) // ❌ (string -> int   ) new type
    .connect(print)
Enter fullscreen mode Exit fullscreen mode

We can get around this limitation by converting .connect() into a top-level connect() function that can change the stream type πŸŽ‰:

func connect[A, B, C any](
    s1 Stream[A, B],
    s2 Stream[B, C],
) Stream[A, C]
Enter fullscreen mode Exit fullscreen mode

Unfortunately, readability suffers a little:

connect(connect(connect(lines, omitEmpty), getLength), print)
Enter fullscreen mode Exit fullscreen mode

Or written with right-associativity:

connect(lines, connect(omitEmpty, connect(getLength, print)))
Enter fullscreen mode Exit fullscreen mode

Or on separate lines:

s1 := lines
s2 := connect(s1, omitEmpty)
s3 := connect(s2, getLength)
s4 := connect(s3, print)
Enter fullscreen mode Exit fullscreen mode

We need separate variables for each step in the stream, but other than that it's not much different from method chaining above!

Top comments (2)

Collapse
 
programmermarvin profile image
Marvin

You can solve this by combining generics with interface within the connect method. This is more of a data issue and not implementation(generics) issue.

Collapse
 
chrismwendt profile image
Chris Wendt

Hmm, I don't follow. Got an example?