Go Generic Channel Type

Issue

I am currently trying to send data over a channel to a goroutine, which will then process it further. My problem is that I want the channel to be able to work with any type. To do this I was looking into the newly introduced generics in Go 1.18.

My problem is that I need to tell the goroutine when I am starting it what type the channel will be, which is impossible to tell since it could hold any data.

This is what I got right now:

Thread:

func StartController[T any](sender chan Packet[T]) {
    go runThread(sender)
}

func runThread[T any](sender chan Packet[T]) {
    fmt.Println("in thread")
    for true {
        data := <- sender

        fmt.Println(data)
    }
}

And my test function is this:

func main() {
    sender := make(chan Packet)

    StartController(sender)

    sender <- Packet[int]{
        Msg: Message[int]{
            Data: 1,
        },
    }

    sender <- Packet[string]{
        Msg: Message[string]{
            Data: "asd",
        },
    }

    for true {}
}

Types:

type Message[T any] struct {
    Data T
}

type Packet[T any] struct {
    Msg Message[T]
}

Right now this code doesn’t compile because of

.\test.go:8:22: cannot use generic type Packet[T interface{}] without instantiation

Is there a way to do this properly?

I was looking into not using generics and just using interface{} as the type, but that would make the entire logic messy since it requires parsing (and might not even be possible since the data could be fairly complex (nested structs))

Solution

That’s a mistaken way of using generics.

A parametrized type, like chan T must be instantiated with a concrete type parameter before you can use it. Given a defined chan type:

type GenericChan[T any] chan T

you would still need to instantiate it with a concrete type:

c := make(GenericChan[int])

which makes using type parameters a bit moot.

I don’t know what your background is, but consider a language where generics have been a stable presence since a long time. E.g. Java. And consider the typical Java generic collector List<T>. What you usually do is instantiating that with a type:

var list = new ArrayList<String>(); 

What you are attempting to do here is to declare a channel which can take any type. In Java, what would be a list that can hold any type?

var list = new ArrayList<Object>(); 

And in Go that would be nothing else than

c := make(chan interface{})

You can look at it another way: how do you expect this generic chan to work on receive operations?

c := make(GenericChan) // wrong syntax: instantiating without type param
c <- "a string"        // let's pretend you can send anything into it

// ...

foo := <-c 

At this point what is foo? Is it a string? Or an int? You can send anything into it. That’s why a generic chan like in your example can not work the way you intend. It must be chan interface{} and then you type-assert the received item like you do now without generics.

The point of generics is to write code that consumes arbitrary types, while maintaining type safety:

func receiveAny[T any](c chan T) T {
    return <-c
}

which you can call with either a chan int or a chan string.

Answered By – blackgreen

Answer Checked By – Marilyn (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.